Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/fallocate.cc
39537 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2021 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
#include <sys/mount.h>
31
#include <sys/resource.h>
32
#include <sys/time.h>
33
34
#include <fcntl.h>
35
#include <mntopts.h> // for build_iovec
36
#include <signal.h>
37
#include <unistd.h>
38
}
39
40
#include "mockfs.hh"
41
#include "utils.hh"
42
43
using namespace testing;
44
45
/* Is buf all zero? */
46
static bool
47
is_zero(const char *buf, uint64_t size)
48
{
49
return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1);
50
}
51
52
class Fallocate: public FuseTest {
53
public:
54
/*
55
* expect VOP_DEALLOCATE to be implemented by vop_stddeallocate.
56
*/
57
void expect_vop_stddeallocate(uint64_t ino, uint64_t off, uint64_t length)
58
{
59
/* XXX read offset and size may depend on cache mode */
60
EXPECT_CALL(*m_mock, process(
61
ResultOf([=](auto in) {
62
return (in.header.opcode == FUSE_READ &&
63
in.header.nodeid == ino &&
64
in.body.read.offset <= off &&
65
in.body.read.offset + in.body.read.size >=
66
off + length);
67
}, Eq(true)),
68
_)
69
).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
70
assert(in.body.read.size <= sizeof(out.body.bytes));
71
out.header.len = sizeof(struct fuse_out_header) +
72
in.body.read.size;
73
memset(out.body.bytes, 'X', in.body.read.size);
74
}))).RetiresOnSaturation();
75
EXPECT_CALL(*m_mock, process(
76
ResultOf([=](auto in) {
77
const char *buf = (const char*)in.body.bytes +
78
sizeof(struct fuse_write_in);
79
80
assert(length <= sizeof(in.body.bytes) -
81
sizeof(struct fuse_write_in));
82
return (in.header.opcode == FUSE_WRITE &&
83
in.header.nodeid == ino &&
84
in.body.write.offset == off &&
85
in.body.write.size == length &&
86
is_zero(buf, length));
87
}, Eq(true)),
88
_)
89
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
90
SET_OUT_HEADER_LEN(out, write);
91
out.body.write.size = length;
92
})));
93
}
94
};
95
96
class Fspacectl: public Fallocate {};
97
98
class Fspacectl_7_18: public Fspacectl {
99
public:
100
virtual void SetUp() {
101
m_kernel_minor_version = 18;
102
Fspacectl::SetUp();
103
}
104
};
105
106
class FspacectlCache: public Fspacectl, public WithParamInterface<cache_mode> {
107
public:
108
bool m_direct_io;
109
110
FspacectlCache(): m_direct_io(false) {};
111
112
virtual void SetUp() {
113
int cache_mode = GetParam();
114
switch (cache_mode) {
115
case Uncached:
116
m_direct_io = true;
117
break;
118
case WritebackAsync:
119
m_async = true;
120
/* FALLTHROUGH */
121
case Writeback:
122
m_init_flags |= FUSE_WRITEBACK_CACHE;
123
/* FALLTHROUGH */
124
case Writethrough:
125
break;
126
default:
127
FAIL() << "Unknown cache mode";
128
}
129
130
FuseTest::SetUp();
131
if (IsSkipped())
132
return;
133
}
134
};
135
136
class PosixFallocate: public Fallocate {
137
public:
138
static sig_atomic_t s_sigxfsz;
139
140
void SetUp() {
141
s_sigxfsz = 0;
142
FuseTest::SetUp();
143
}
144
145
void TearDown() {
146
struct sigaction sa;
147
148
bzero(&sa, sizeof(sa));
149
sa.sa_handler = SIG_DFL;
150
sigaction(SIGXFSZ, &sa, NULL);
151
152
Fallocate::TearDown();
153
}
154
155
};
156
157
sig_atomic_t PosixFallocate::s_sigxfsz = 0;
158
159
void sigxfsz_handler(int __unused sig) {
160
PosixFallocate::s_sigxfsz = 1;
161
}
162
163
class PosixFallocate_7_18: public PosixFallocate {
164
public:
165
virtual void SetUp() {
166
m_kernel_minor_version = 18;
167
PosixFallocate::SetUp();
168
}
169
};
170
171
172
/*
173
* If the server returns ENOSYS, it indicates that the server does not support
174
* FUSE_FALLOCATE. This and future calls should fall back to vop_stddeallocate.
175
*/
176
TEST_F(Fspacectl, enosys)
177
{
178
const char FULLPATH[] = "mountpoint/some_file.txt";
179
const char RELPATH[] = "some_file.txt";
180
off_t fsize = 1 << 20;
181
off_t off0 = 100;
182
off_t len0 = 500;
183
struct spacectl_range rqsr = { .r_offset = off0, .r_len = len0 };
184
uint64_t ino = 42;
185
uint64_t off1 = fsize;
186
uint64_t len1 = 1000;
187
off_t off2 = fsize / 2;
188
off_t len2 = 500;
189
int fd;
190
191
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
192
expect_open(ino, 0, 1);
193
expect_fallocate(ino, off0, len0,
194
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, ENOSYS);
195
expect_vop_stddeallocate(ino, off0, len0);
196
expect_vop_stddeallocate(ino, off2, len2);
197
198
fd = open(FULLPATH, O_RDWR);
199
ASSERT_LE(0, fd) << strerror(errno);
200
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
201
202
/* Subsequent calls shouldn't query the daemon either */
203
rqsr.r_offset = off2;
204
rqsr.r_len = len2;
205
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
206
207
/* Neither should posix_fallocate query the daemon */
208
EXPECT_EQ(EINVAL, posix_fallocate(fd, off1, len1));
209
210
leak(fd);
211
}
212
213
/*
214
* EOPNOTSUPP means "the file system does not support fallocate with the
215
* supplied mode on this particular file". So we should fallback, but not
216
* assume anything about whether the operation will fail on a different file or
217
* with a different mode.
218
*/
219
TEST_F(Fspacectl, eopnotsupp)
220
{
221
const char FULLPATH[] = "mountpoint/some_file.txt";
222
const char RELPATH[] = "some_file.txt";
223
struct spacectl_range rqsr;
224
uint64_t ino = 42;
225
uint64_t fsize = 1 << 20;
226
uint64_t off0 = 500;
227
uint64_t len = 1000;
228
uint64_t off1 = fsize / 2;
229
int fd;
230
231
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
232
expect_open(ino, 0, 1);
233
expect_fallocate(ino, off0, len,
234
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE,
235
EOPNOTSUPP);
236
expect_vop_stddeallocate(ino, off0, len);
237
expect_fallocate(ino, off1, len,
238
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE,
239
EOPNOTSUPP);
240
expect_vop_stddeallocate(ino, off1, len);
241
expect_fallocate(ino, fsize, len, 0, 0);
242
243
fd = open(FULLPATH, O_RDWR);
244
ASSERT_LE(0, fd) << strerror(errno);
245
246
/*
247
* Though the FUSE daemon will reject the call, the kernel should fall
248
* back to a read-modify-write approach.
249
*/
250
rqsr.r_offset = off0;
251
rqsr.r_len = len;
252
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
253
254
/* Subsequent calls should still query the daemon */
255
rqsr.r_offset = off1;
256
rqsr.r_len = len;
257
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
258
259
/* But subsequent posix_fallocate calls _should_ query the daemon */
260
EXPECT_EQ(0, posix_fallocate(fd, fsize, len));
261
262
leak(fd);
263
}
264
265
TEST_F(Fspacectl, erofs)
266
{
267
const char FULLPATH[] = "mountpoint/some_file.txt";
268
const char RELPATH[] = "some_file.txt";
269
struct statfs statbuf;
270
uint64_t fsize = 2000;
271
struct spacectl_range rqsr = { .r_offset = 0, .r_len = 1 };
272
struct iovec *iov = NULL;
273
int iovlen = 0;
274
uint64_t ino = 42;
275
int fd;
276
int newflags;
277
278
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
279
expect_open(ino, 0, 1);
280
EXPECT_CALL(*m_mock, process(
281
ResultOf([](auto in) {
282
return (in.header.opcode == FUSE_STATFS);
283
}, Eq(true)),
284
_)
285
).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
286
{
287
/*
288
* All of the fields except f_flags are don't care, and f_flags
289
* is set by the VFS
290
*/
291
SET_OUT_HEADER_LEN(out, statfs);
292
})));
293
294
fd = open(FULLPATH, O_RDWR);
295
ASSERT_LE(0, fd) << strerror(errno);
296
297
/* Remount read-only */
298
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
299
newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
300
build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
301
build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
302
build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
303
ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
304
free_iovec(&iov, &iovlen);
305
306
EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
307
EXPECT_EQ(EROFS, errno);
308
309
leak(fd);
310
}
311
312
/*
313
* If FUSE_GETATTR fails when determining the size of the file, fspacectl
314
* should fail gracefully. This failure mode is easiest to trigger when
315
* attribute caching is disabled.
316
*/
317
TEST_F(Fspacectl, getattr_fails)
318
{
319
const char FULLPATH[] = "mountpoint/some_file.txt";
320
const char RELPATH[] = "some_file.txt";
321
Sequence seq;
322
struct spacectl_range rqsr;
323
const uint64_t ino = 42;
324
const uint64_t fsize = 2000;
325
int fd;
326
327
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0);
328
expect_open(ino, 0, 1);
329
EXPECT_CALL(*m_mock, process(
330
ResultOf([](auto in) {
331
return (in.header.opcode == FUSE_GETATTR &&
332
in.header.nodeid == ino);
333
}, Eq(true)),
334
_)
335
).Times(1)
336
.InSequence(seq)
337
.WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
338
SET_OUT_HEADER_LEN(out, attr);
339
out.body.attr.attr.ino = ino;
340
out.body.attr.attr.mode = S_IFREG | 0644;
341
out.body.attr.attr.size = fsize;
342
out.body.attr.attr_valid = 0;
343
})));
344
EXPECT_CALL(*m_mock, process(
345
ResultOf([](auto in) {
346
return (in.header.opcode == FUSE_GETATTR &&
347
in.header.nodeid == ino);
348
}, Eq(true)),
349
_)
350
).InSequence(seq)
351
.WillOnce(ReturnErrno(EIO));
352
353
fd = open(FULLPATH, O_RDWR);
354
ASSERT_LE(0, fd) << strerror(errno);
355
rqsr.r_offset = 500;
356
rqsr.r_len = 1000;
357
EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
358
EXPECT_EQ(EIO, errno);
359
360
leak(fd);
361
}
362
363
TEST_F(Fspacectl, ok)
364
{
365
const char FULLPATH[] = "mountpoint/some_file.txt";
366
const char RELPATH[] = "some_file.txt";
367
struct spacectl_range rqsr, rmsr;
368
struct stat sb0, sb1;
369
uint64_t ino = 42;
370
uint64_t fsize = 2000;
371
uint64_t offset = 500;
372
uint64_t length = 1000;
373
int fd;
374
375
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
376
expect_open(ino, 0, 1);
377
expect_fallocate(ino, offset, length,
378
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
379
380
fd = open(FULLPATH, O_RDWR);
381
ASSERT_LE(0, fd) << strerror(errno);
382
ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
383
rqsr.r_offset = offset;
384
rqsr.r_len = length;
385
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
386
EXPECT_EQ(0, rmsr.r_len);
387
EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
388
389
/*
390
* The file's attributes should not have been invalidated, so this fstat
391
* will not requery the daemon.
392
*/
393
EXPECT_EQ(0, fstat(fd, &sb1));
394
EXPECT_EQ(fsize, (uint64_t)sb1.st_size);
395
396
/* mtime and ctime should be updated */
397
EXPECT_EQ(sb0.st_atime, sb1.st_atime);
398
EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
399
EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
400
401
leak(fd);
402
}
403
404
/* The returned rqsr.r_off should be clipped at EoF */
405
TEST_F(Fspacectl, past_eof)
406
{
407
const char FULLPATH[] = "mountpoint/some_file.txt";
408
const char RELPATH[] = "some_file.txt";
409
struct spacectl_range rqsr, rmsr;
410
uint64_t ino = 42;
411
uint64_t fsize = 1000;
412
uint64_t offset = 1500;
413
uint64_t length = 1000;
414
int fd;
415
416
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
417
expect_open(ino, 0, 1);
418
expect_fallocate(ino, offset, length,
419
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
420
421
fd = open(FULLPATH, O_RDWR);
422
ASSERT_LE(0, fd) << strerror(errno);
423
rqsr.r_offset = offset;
424
rqsr.r_len = length;
425
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
426
EXPECT_EQ(0, rmsr.r_len);
427
EXPECT_EQ((off_t)fsize, rmsr.r_offset);
428
429
leak(fd);
430
}
431
432
/* The returned rqsr.r_off should be clipped at EoF */
433
TEST_F(Fspacectl, spans_eof)
434
{
435
const char FULLPATH[] = "mountpoint/some_file.txt";
436
const char RELPATH[] = "some_file.txt";
437
struct spacectl_range rqsr, rmsr;
438
uint64_t ino = 42;
439
uint64_t fsize = 1000;
440
uint64_t offset = 500;
441
uint64_t length = 1000;
442
int fd;
443
444
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
445
expect_open(ino, 0, 1);
446
expect_fallocate(ino, offset, length,
447
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
448
449
fd = open(FULLPATH, O_RDWR);
450
ASSERT_LE(0, fd) << strerror(errno);
451
rqsr.r_offset = offset;
452
rqsr.r_len = length;
453
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
454
EXPECT_EQ(0, rmsr.r_len);
455
EXPECT_EQ((off_t)fsize, rmsr.r_offset);
456
457
leak(fd);
458
}
459
460
/*
461
* With older servers, no FUSE_FALLOCATE should be attempted. The kernel
462
* should fall back to vop_stddeallocate.
463
*/
464
TEST_F(Fspacectl_7_18, ok)
465
{
466
const char FULLPATH[] = "mountpoint/some_file.txt";
467
const char RELPATH[] = "some_file.txt";
468
struct spacectl_range rqsr, rmsr;
469
char *buf;
470
uint64_t ino = 42;
471
uint64_t fsize = 2000;
472
uint64_t offset = 500;
473
uint64_t length = 1000;
474
int fd;
475
476
buf = new char[length];
477
478
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
479
expect_open(ino, 0, 1);
480
expect_vop_stddeallocate(ino, offset, length);
481
482
fd = open(FULLPATH, O_RDWR);
483
ASSERT_LE(0, fd) << strerror(errno);
484
rqsr.r_offset = offset;
485
rqsr.r_len = length;
486
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
487
EXPECT_EQ(0, rmsr.r_len);
488
EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
489
490
leak(fd);
491
delete[] buf;
492
}
493
494
/*
495
* A successful fspacectl should clear the zeroed data from the kernel cache.
496
*/
497
TEST_P(FspacectlCache, clears_cache)
498
{
499
const char FULLPATH[] = "mountpoint/some_file.txt";
500
const char RELPATH[] = "some_file.txt";
501
const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
502
struct spacectl_range rqsr, rmsr;
503
uint64_t ino = 42;
504
ssize_t bufsize = strlen(CONTENTS);
505
uint64_t fsize = bufsize;
506
uint8_t buf[bufsize];
507
char zbuf[bufsize];
508
uint64_t offset = 0;
509
uint64_t length = bufsize;
510
int fd;
511
512
bzero(zbuf, bufsize);
513
514
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
515
expect_open(ino, 0, 1);
516
/* NB: expectations are applied in LIFO order */
517
expect_read(ino, 0, fsize, fsize, zbuf);
518
expect_read(ino, 0, fsize, fsize, CONTENTS);
519
expect_fallocate(ino, offset, length,
520
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
521
522
fd = open(FULLPATH, O_RDWR);
523
ASSERT_LE(0, fd) << strerror(errno);
524
525
/* Populate the cache */
526
ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
527
<< strerror(errno);
528
ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize));
529
530
/* Zero the file */
531
rqsr.r_offset = offset;
532
rqsr.r_len = length;
533
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
534
EXPECT_EQ(0, rmsr.r_len);
535
EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
536
537
/* Read again. This should query the daemon */
538
ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
539
<< strerror(errno);
540
ASSERT_EQ(0, memcmp(buf, zbuf, fsize));
541
542
leak(fd);
543
}
544
545
INSTANTIATE_TEST_SUITE_P(FspacectlCache, FspacectlCache,
546
Values(Uncached, Writethrough, Writeback)
547
);
548
549
/*
550
* If the server returns ENOSYS, it indicates that the server does not support
551
* FUSE_FALLOCATE. This and future calls should return EINVAL.
552
*/
553
TEST_F(PosixFallocate, enosys)
554
{
555
const char FULLPATH[] = "mountpoint/some_file.txt";
556
const char RELPATH[] = "some_file.txt";
557
uint64_t ino = 42;
558
uint64_t off0 = 0;
559
uint64_t len0 = 1000;
560
off_t off1 = 100;
561
off_t len1 = 200;
562
uint64_t fsize = 500;
563
struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 };
564
int fd;
565
566
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
567
expect_open(ino, 0, 1);
568
expect_fallocate(ino, off0, len0, 0, ENOSYS);
569
expect_vop_stddeallocate(ino, off1, len1);
570
571
fd = open(FULLPATH, O_RDWR);
572
ASSERT_LE(0, fd) << strerror(errno);
573
EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
574
575
/* Subsequent calls shouldn't query the daemon*/
576
EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
577
578
/* Neither should VOP_DEALLOCATE query the daemon */
579
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
580
581
leak(fd);
582
}
583
584
/*
585
* EOPNOTSUPP means "the file system does not support fallocate with the
586
* supplied mode on this particular file". So we should fallback, but not
587
* assume anything about whether the operation will fail on a different file or
588
* with a different mode.
589
*/
590
TEST_F(PosixFallocate, eopnotsupp)
591
{
592
const char FULLPATH[] = "mountpoint/some_file.txt";
593
const char RELPATH[] = "some_file.txt";
594
struct spacectl_range rqsr;
595
uint64_t ino = 42;
596
uint64_t fsize = 2000;
597
uint64_t offset = 0;
598
uint64_t length = 1000;
599
int fd;
600
601
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
602
expect_open(ino, 0, 1);
603
expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP);
604
expect_fallocate(ino, offset, length, 0, EOPNOTSUPP);
605
expect_fallocate(ino, offset, length,
606
FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
607
608
fd = open(FULLPATH, O_RDWR);
609
ASSERT_LE(0, fd) << strerror(errno);
610
EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length));
611
612
/* Subsequent calls should still query the daemon*/
613
EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
614
615
/* And subsequent VOP_DEALLOCATE calls should also query the daemon */
616
rqsr.r_len = length;
617
rqsr.r_offset = offset;
618
EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
619
620
leak(fd);
621
}
622
623
/* EIO is not a permanent error, and may be retried */
624
TEST_F(PosixFallocate, eio)
625
{
626
const char FULLPATH[] = "mountpoint/some_file.txt";
627
const char RELPATH[] = "some_file.txt";
628
uint64_t ino = 42;
629
uint64_t offset = 0;
630
uint64_t length = 1000;
631
int fd;
632
633
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
634
expect_open(ino, 0, 1);
635
expect_fallocate(ino, offset, length, 0, EIO);
636
637
fd = open(FULLPATH, O_RDWR);
638
ASSERT_LE(0, fd) << strerror(errno);
639
EXPECT_EQ(EIO, posix_fallocate(fd, offset, length));
640
641
expect_fallocate(ino, offset, length, 0, 0);
642
643
EXPECT_EQ(0, posix_fallocate(fd, offset, length));
644
645
leak(fd);
646
}
647
648
TEST_F(PosixFallocate, erofs)
649
{
650
const char FULLPATH[] = "mountpoint/some_file.txt";
651
const char RELPATH[] = "some_file.txt";
652
struct statfs statbuf;
653
struct iovec *iov = NULL;
654
int iovlen = 0;
655
uint64_t ino = 42;
656
uint64_t offset = 0;
657
uint64_t length = 1000;
658
int fd;
659
int newflags;
660
661
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
662
expect_open(ino, 0, 1);
663
EXPECT_CALL(*m_mock, process(
664
ResultOf([](auto in) {
665
return (in.header.opcode == FUSE_STATFS);
666
}, Eq(true)),
667
_)
668
).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
669
{
670
/*
671
* All of the fields except f_flags are don't care, and f_flags
672
* is set by the VFS
673
*/
674
SET_OUT_HEADER_LEN(out, statfs);
675
})));
676
677
fd = open(FULLPATH, O_RDWR);
678
ASSERT_LE(0, fd) << strerror(errno);
679
680
/* Remount read-only */
681
ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
682
newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
683
build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
684
build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
685
build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
686
ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
687
free_iovec(&iov, &iovlen);
688
689
EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length));
690
691
leak(fd);
692
}
693
694
TEST_F(PosixFallocate, ok)
695
{
696
const char FULLPATH[] = "mountpoint/some_file.txt";
697
const char RELPATH[] = "some_file.txt";
698
struct stat sb0, sb1;
699
uint64_t ino = 42;
700
uint64_t offset = 0;
701
uint64_t length = 1000;
702
int fd;
703
704
EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
705
.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
706
SET_OUT_HEADER_LEN(out, entry);
707
out.body.entry.attr.mode = S_IFREG | 0644;
708
out.body.entry.nodeid = ino;
709
out.body.entry.entry_valid = UINT64_MAX;
710
out.body.entry.attr_valid = UINT64_MAX;
711
})));
712
expect_open(ino, 0, 1);
713
expect_fallocate(ino, offset, length, 0, 0);
714
715
fd = open(FULLPATH, O_RDWR);
716
ASSERT_LE(0, fd) << strerror(errno);
717
ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
718
EXPECT_EQ(0, posix_fallocate(fd, offset, length));
719
/*
720
* Despite the originally cached file size of zero, stat should now
721
* return either the new size or requery the daemon.
722
*/
723
EXPECT_EQ(0, stat(FULLPATH, &sb1));
724
EXPECT_EQ(length, (uint64_t)sb1.st_size);
725
726
/* mtime and ctime should be updated */
727
EXPECT_EQ(sb0.st_atime, sb1.st_atime);
728
EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
729
EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
730
731
leak(fd);
732
}
733
734
/* fusefs should respect RLIMIT_FSIZE */
735
TEST_F(PosixFallocate, rlimit_fsize)
736
{
737
const char FULLPATH[] = "mountpoint/some_file.txt";
738
const char RELPATH[] = "some_file.txt";
739
struct rlimit rl;
740
uint64_t ino = 42;
741
uint64_t offset = 0;
742
uint64_t length = 1'000'000;
743
int fd;
744
745
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
746
expect_open(ino, 0, 1);
747
748
rl.rlim_cur = length / 2;
749
rl.rlim_max = 10 * length;
750
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
751
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
752
753
fd = open(FULLPATH, O_RDWR);
754
ASSERT_LE(0, fd) << strerror(errno);
755
EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length));
756
EXPECT_EQ(1, s_sigxfsz);
757
758
leak(fd);
759
}
760
761
/* With older servers, no FUSE_FALLOCATE should be attempted */
762
TEST_F(PosixFallocate_7_18, einval)
763
{
764
const char FULLPATH[] = "mountpoint/some_file.txt";
765
const char RELPATH[] = "some_file.txt";
766
uint64_t ino = 42;
767
uint64_t offset = 0;
768
uint64_t length = 1000;
769
int fd;
770
771
expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
772
expect_open(ino, 0, 1);
773
774
fd = open(FULLPATH, O_RDWR);
775
ASSERT_LE(0, fd) << strerror(errno);
776
EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
777
778
leak(fd);
779
}
780
781