Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/fs/fusefs/copy_file_range.cc
39537 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
#include <sys/mman.h>
31
#include <sys/time.h>
32
#include <sys/resource.h>
33
34
#include <fcntl.h>
35
#include <signal.h>
36
#include <unistd.h>
37
}
38
39
#include "mockfs.hh"
40
#include "utils.hh"
41
42
using namespace testing;
43
44
class CopyFileRange: public FuseTest {
45
public:
46
47
void expect_maybe_lseek(uint64_t ino)
48
{
49
EXPECT_CALL(*m_mock, process(
50
ResultOf([=](auto in) {
51
return (in.header.opcode == FUSE_LSEEK &&
52
in.header.nodeid == ino);
53
}, Eq(true)),
54
_)
55
).Times(AtMost(1))
56
.WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
57
}
58
59
void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh)
60
{
61
EXPECT_CALL(*m_mock, process(
62
ResultOf([=](auto in) {
63
return (in.header.opcode == FUSE_OPEN &&
64
in.header.nodeid == ino);
65
}, Eq(true)),
66
_)
67
).Times(times)
68
.WillRepeatedly(Invoke(
69
ReturnImmediate([=](auto in __unused, auto& out) {
70
out.header.len = sizeof(out.header);
71
SET_OUT_HEADER_LEN(out, open);
72
out.body.open.fh = fh;
73
out.body.open.open_flags = flags;
74
})));
75
}
76
77
void expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
78
uint64_t osize, const void *contents)
79
{
80
EXPECT_CALL(*m_mock, process(
81
ResultOf([=](auto in) {
82
const char *buf = (const char*)in.body.bytes +
83
sizeof(struct fuse_write_in);
84
85
return (in.header.opcode == FUSE_WRITE &&
86
in.header.nodeid == ino &&
87
in.body.write.offset == offset &&
88
in.body.write.size == isize &&
89
0 == bcmp(buf, contents, isize));
90
}, Eq(true)),
91
_)
92
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
93
SET_OUT_HEADER_LEN(out, write);
94
out.body.write.size = osize;
95
})));
96
}
97
98
};
99
100
101
class CopyFileRange_7_27: public CopyFileRange {
102
public:
103
virtual void SetUp() {
104
m_kernel_minor_version = 27;
105
CopyFileRange::SetUp();
106
}
107
};
108
109
class CopyFileRangeNoAtime: public CopyFileRange {
110
public:
111
virtual void SetUp() {
112
m_noatime = true;
113
CopyFileRange::SetUp();
114
}
115
};
116
117
class CopyFileRangeRlimitFsize: public CopyFileRange {
118
public:
119
static sig_atomic_t s_sigxfsz;
120
struct rlimit m_initial_limit;
121
122
virtual void SetUp() {
123
s_sigxfsz = 0;
124
getrlimit(RLIMIT_FSIZE, &m_initial_limit);
125
CopyFileRange::SetUp();
126
}
127
128
void TearDown() {
129
struct sigaction sa;
130
131
setrlimit(RLIMIT_FSIZE, &m_initial_limit);
132
133
bzero(&sa, sizeof(sa));
134
sa.sa_handler = SIG_DFL;
135
sigaction(SIGXFSZ, &sa, NULL);
136
137
FuseTest::TearDown();
138
}
139
140
};
141
142
sig_atomic_t CopyFileRangeRlimitFsize::s_sigxfsz = 0;
143
144
void sigxfsz_handler(int __unused sig) {
145
CopyFileRangeRlimitFsize::s_sigxfsz = 1;
146
}
147
148
TEST_F(CopyFileRange, eio)
149
{
150
const char FULLPATH1[] = "mountpoint/src.txt";
151
const char RELPATH1[] = "src.txt";
152
const char FULLPATH2[] = "mountpoint/dst.txt";
153
const char RELPATH2[] = "dst.txt";
154
const uint64_t ino1 = 42;
155
const uint64_t ino2 = 43;
156
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
157
const uint64_t fh2 = 0xdeadc0de88c0ffee;
158
off_t fsize1 = 1 << 20; /* 1 MiB */
159
off_t fsize2 = 1 << 19; /* 512 KiB */
160
off_t start1 = 1 << 18;
161
off_t start2 = 3 << 17;
162
ssize_t len = 65536;
163
int fd1, fd2;
164
165
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
166
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
167
expect_open(ino1, 0, 1, fh1);
168
expect_open(ino2, 0, 1, fh2);
169
EXPECT_CALL(*m_mock, process(
170
ResultOf([=](auto in) {
171
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
172
in.header.nodeid == ino1 &&
173
in.body.copy_file_range.fh_in == fh1 &&
174
(off_t)in.body.copy_file_range.off_in == start1 &&
175
in.body.copy_file_range.nodeid_out == ino2 &&
176
in.body.copy_file_range.fh_out == fh2 &&
177
(off_t)in.body.copy_file_range.off_out == start2 &&
178
in.body.copy_file_range.len == (size_t)len &&
179
in.body.copy_file_range.flags == 0);
180
}, Eq(true)),
181
_)
182
).WillOnce(Invoke(ReturnErrno(EIO)));
183
184
fd1 = open(FULLPATH1, O_RDONLY);
185
fd2 = open(FULLPATH2, O_WRONLY);
186
ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
187
EXPECT_EQ(EIO, errno);
188
}
189
190
/*
191
* copy_file_range should evict cached data for the modified region of the
192
* destination file.
193
*/
194
TEST_F(CopyFileRange, evicts_cache)
195
{
196
const char FULLPATH1[] = "mountpoint/src.txt";
197
const char RELPATH1[] = "src.txt";
198
const char FULLPATH2[] = "mountpoint/dst.txt";
199
const char RELPATH2[] = "dst.txt";
200
char *buf0, *buf1, *buf;
201
const uint64_t ino1 = 42;
202
const uint64_t ino2 = 43;
203
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
204
const uint64_t fh2 = 0xdeadc0de88c0ffee;
205
off_t fsize1 = 1 << 20; /* 1 MiB */
206
off_t fsize2 = 1 << 19; /* 512 KiB */
207
off_t start1 = 1 << 18;
208
off_t start2 = 3 << 17;
209
ssize_t len = m_maxbcachebuf;
210
int fd1, fd2;
211
212
buf0 = new char[m_maxbcachebuf];
213
memset(buf0, 42, m_maxbcachebuf);
214
215
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
216
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
217
expect_open(ino1, 0, 1, fh1);
218
expect_open(ino2, 0, 1, fh2);
219
expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf0, -1,
220
fh2);
221
EXPECT_CALL(*m_mock, process(
222
ResultOf([=](auto in) {
223
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
224
in.header.nodeid == ino1 &&
225
in.body.copy_file_range.fh_in == fh1 &&
226
(off_t)in.body.copy_file_range.off_in == start1 &&
227
in.body.copy_file_range.nodeid_out == ino2 &&
228
in.body.copy_file_range.fh_out == fh2 &&
229
(off_t)in.body.copy_file_range.off_out == start2 &&
230
in.body.copy_file_range.len == (size_t)len &&
231
in.body.copy_file_range.flags == 0);
232
}, Eq(true)),
233
_)
234
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
235
SET_OUT_HEADER_LEN(out, write);
236
out.body.write.size = len;
237
})));
238
239
fd1 = open(FULLPATH1, O_RDONLY);
240
fd2 = open(FULLPATH2, O_RDWR);
241
242
// Prime cache
243
buf = new char[m_maxbcachebuf];
244
ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
245
<< strerror(errno);
246
EXPECT_EQ(0, memcmp(buf0, buf, m_maxbcachebuf));
247
248
// Tell the FUSE server overwrite the region we just read
249
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
250
251
// Read again. This should bypass the cache and read direct from server
252
buf1 = new char[m_maxbcachebuf];
253
memset(buf1, 69, m_maxbcachebuf);
254
start2 -= len;
255
expect_read(ino2, start2, m_maxbcachebuf, m_maxbcachebuf, buf1, -1,
256
fh2);
257
ASSERT_EQ(m_maxbcachebuf, pread(fd2, buf, m_maxbcachebuf, start2))
258
<< strerror(errno);
259
EXPECT_EQ(0, memcmp(buf1, buf, m_maxbcachebuf));
260
261
delete[] buf1;
262
delete[] buf0;
263
delete[] buf;
264
leak(fd1);
265
leak(fd2);
266
}
267
268
/*
269
* If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should
270
* fallback to a read/write based implementation.
271
*/
272
TEST_F(CopyFileRange, fallback)
273
{
274
const char FULLPATH1[] = "mountpoint/src.txt";
275
const char RELPATH1[] = "src.txt";
276
const char FULLPATH2[] = "mountpoint/dst.txt";
277
const char RELPATH2[] = "dst.txt";
278
const uint64_t ino1 = 42;
279
const uint64_t ino2 = 43;
280
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
281
const uint64_t fh2 = 0xdeadc0de88c0ffee;
282
off_t fsize2 = 0;
283
off_t start1 = 0;
284
off_t start2 = 0;
285
const char *contents = "Hello, world!";
286
ssize_t len;
287
int fd1, fd2;
288
289
len = strlen(contents);
290
291
/*
292
* Ensure that we read to EOF, just so the buffer cache's read size is
293
* predictable.
294
*/
295
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
296
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
297
expect_open(ino1, 0, 1, fh1);
298
expect_open(ino2, 0, 1, fh2);
299
EXPECT_CALL(*m_mock, process(
300
ResultOf([=](auto in) {
301
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
302
in.header.nodeid == ino1 &&
303
in.body.copy_file_range.fh_in == fh1 &&
304
(off_t)in.body.copy_file_range.off_in == start1 &&
305
in.body.copy_file_range.nodeid_out == ino2 &&
306
in.body.copy_file_range.fh_out == fh2 &&
307
(off_t)in.body.copy_file_range.off_out == start2 &&
308
in.body.copy_file_range.len == (size_t)len &&
309
in.body.copy_file_range.flags == 0);
310
}, Eq(true)),
311
_)
312
).WillOnce(Invoke(ReturnErrno(ENOSYS)));
313
expect_maybe_lseek(ino1);
314
expect_read(ino1, start1, len, len, contents, 0);
315
expect_write(ino2, start2, len, len, contents);
316
317
fd1 = open(FULLPATH1, O_RDONLY);
318
ASSERT_GE(fd1, 0);
319
fd2 = open(FULLPATH2, O_WRONLY);
320
ASSERT_GE(fd2, 0);
321
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
322
}
323
324
/*
325
* Writes via mmap should not conflict with using copy_file_range. Any dirty
326
* pages that overlap with copy_file_range's input should be flushed before
327
* FUSE_COPY_FILE_RANGE is sent.
328
*/
329
TEST_F(CopyFileRange, mmap_write)
330
{
331
const char FULLPATH[] = "mountpoint/src.txt";
332
const char RELPATH[] = "src.txt";
333
uint8_t *wbuf, *fbuf;
334
void *p;
335
size_t fsize = 0x6000;
336
size_t wsize = 0x3000;
337
ssize_t r;
338
off_t offset2_in = 0;
339
off_t offset2_out = wsize;
340
size_t copysize = wsize;
341
const uint64_t ino = 42;
342
const uint64_t fh = 0xdeadbeef1a7ebabe;
343
int fd;
344
const mode_t mode = 0644;
345
346
fbuf = new uint8_t[fsize]();
347
wbuf = new uint8_t[wsize];
348
memset(wbuf, 1, wsize);
349
350
expect_lookup(RELPATH, ino, S_IFREG | mode, fsize, 1);
351
expect_open(ino, 0, 1, fh);
352
/* This read is initiated by the mmap write */
353
expect_read(ino, 0, fsize, fsize, fbuf, -1, fh);
354
/* This write flushes the buffer filled by the mmap write */
355
expect_write(ino, 0, wsize, wsize, wbuf);
356
357
EXPECT_CALL(*m_mock, process(
358
ResultOf([=](auto in) {
359
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
360
(off_t)in.body.copy_file_range.off_in == offset2_in &&
361
(off_t)in.body.copy_file_range.off_out == offset2_out &&
362
in.body.copy_file_range.len == copysize
363
);
364
}, Eq(true)),
365
_)
366
).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
367
SET_OUT_HEADER_LEN(out, write);
368
out.body.write.size = copysize;
369
})));
370
371
fd = open(FULLPATH, O_RDWR);
372
373
/* First, write some data via mmap */
374
p = mmap(NULL, wsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
375
ASSERT_NE(MAP_FAILED, p) << strerror(errno);
376
memmove((uint8_t*)p, wbuf, wsize);
377
ASSERT_EQ(0, munmap(p, wsize)) << strerror(errno);
378
379
/*
380
* Then copy it around the file via copy_file_range. This should
381
* trigger a FUSE_WRITE to flush the pages written by mmap.
382
*/
383
r = copy_file_range(fd, &offset2_in, fd, &offset2_out, copysize, 0);
384
ASSERT_EQ(copysize, (size_t)r) << strerror(errno);
385
386
delete[] wbuf;
387
delete[] fbuf;
388
}
389
390
391
/*
392
* copy_file_range should send SIGXFSZ and return EFBIG when the operation
393
* would exceed the limit imposed by RLIMIT_FSIZE.
394
*/
395
TEST_F(CopyFileRangeRlimitFsize, signal)
396
{
397
const char FULLPATH1[] = "mountpoint/src.txt";
398
const char RELPATH1[] = "src.txt";
399
const char FULLPATH2[] = "mountpoint/dst.txt";
400
const char RELPATH2[] = "dst.txt";
401
struct rlimit rl;
402
const uint64_t ino1 = 42;
403
const uint64_t ino2 = 43;
404
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
405
const uint64_t fh2 = 0xdeadc0de88c0ffee;
406
off_t fsize1 = 1 << 20; /* 1 MiB */
407
off_t fsize2 = 1 << 19; /* 512 KiB */
408
off_t start1 = 1 << 18;
409
off_t start2 = fsize2;
410
ssize_t len = 65536;
411
int fd1, fd2;
412
413
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
414
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
415
expect_open(ino1, 0, 1, fh1);
416
expect_open(ino2, 0, 1, fh2);
417
EXPECT_CALL(*m_mock, process(
418
ResultOf([=](auto in) {
419
return (in.header.opcode == FUSE_COPY_FILE_RANGE);
420
}, Eq(true)),
421
_)
422
).Times(0);
423
424
rl.rlim_cur = fsize2;
425
rl.rlim_max = m_initial_limit.rlim_max;
426
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
427
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
428
429
fd1 = open(FULLPATH1, O_RDONLY);
430
fd2 = open(FULLPATH2, O_WRONLY);
431
ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
432
EXPECT_EQ(EFBIG, errno);
433
EXPECT_EQ(1, s_sigxfsz);
434
}
435
436
/*
437
* When crossing the RLIMIT_FSIZE boundary, writes should be truncated, not
438
* aborted.
439
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=266611
440
*/
441
TEST_F(CopyFileRangeRlimitFsize, truncate)
442
{
443
const char FULLPATH1[] = "mountpoint/src.txt";
444
const char RELPATH1[] = "src.txt";
445
const char FULLPATH2[] = "mountpoint/dst.txt";
446
const char RELPATH2[] = "dst.txt";
447
struct rlimit rl;
448
const uint64_t ino1 = 42;
449
const uint64_t ino2 = 43;
450
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
451
const uint64_t fh2 = 0xdeadc0de88c0ffee;
452
off_t fsize1 = 1 << 20; /* 1 MiB */
453
off_t fsize2 = 1 << 19; /* 512 KiB */
454
off_t start1 = 1 << 18;
455
off_t start2 = fsize2;
456
ssize_t len = 65536;
457
off_t limit = start2 + len / 2;
458
int fd1, fd2;
459
460
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
461
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
462
expect_open(ino1, 0, 1, fh1);
463
expect_open(ino2, 0, 1, fh2);
464
EXPECT_CALL(*m_mock, process(
465
ResultOf([=](auto in) {
466
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
467
(off_t)in.body.copy_file_range.off_out == start2 &&
468
in.body.copy_file_range.len == (size_t)len / 2
469
);
470
}, Eq(true)),
471
_)
472
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
473
SET_OUT_HEADER_LEN(out, write);
474
out.body.write.size = len / 2;
475
})));
476
477
rl.rlim_cur = limit;
478
rl.rlim_max = m_initial_limit.rlim_max;
479
ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
480
ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
481
482
fd1 = open(FULLPATH1, O_RDONLY);
483
fd2 = open(FULLPATH2, O_WRONLY);
484
ASSERT_EQ(len / 2, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
485
}
486
487
TEST_F(CopyFileRange, ok)
488
{
489
const char FULLPATH1[] = "mountpoint/src.txt";
490
const char RELPATH1[] = "src.txt";
491
const char FULLPATH2[] = "mountpoint/dst.txt";
492
const char RELPATH2[] = "dst.txt";
493
const uint64_t ino1 = 42;
494
const uint64_t ino2 = 43;
495
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
496
const uint64_t fh2 = 0xdeadc0de88c0ffee;
497
off_t fsize1 = 1 << 20; /* 1 MiB */
498
off_t fsize2 = 1 << 19; /* 512 KiB */
499
off_t start1 = 1 << 18;
500
off_t start2 = 3 << 17;
501
ssize_t len = 65536;
502
int fd1, fd2;
503
504
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
505
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
506
expect_open(ino1, 0, 1, fh1);
507
expect_open(ino2, 0, 1, fh2);
508
EXPECT_CALL(*m_mock, process(
509
ResultOf([=](auto in) {
510
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
511
in.header.nodeid == ino1 &&
512
in.body.copy_file_range.fh_in == fh1 &&
513
(off_t)in.body.copy_file_range.off_in == start1 &&
514
in.body.copy_file_range.nodeid_out == ino2 &&
515
in.body.copy_file_range.fh_out == fh2 &&
516
(off_t)in.body.copy_file_range.off_out == start2 &&
517
in.body.copy_file_range.len == (size_t)len &&
518
in.body.copy_file_range.flags == 0);
519
}, Eq(true)),
520
_)
521
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
522
SET_OUT_HEADER_LEN(out, write);
523
out.body.write.size = len;
524
})));
525
526
fd1 = open(FULLPATH1, O_RDONLY);
527
fd2 = open(FULLPATH2, O_WRONLY);
528
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
529
}
530
531
/*
532
* copy_file_range can make copies within a single file, as long as the ranges
533
* don't overlap.
534
* */
535
TEST_F(CopyFileRange, same_file)
536
{
537
const char FULLPATH[] = "mountpoint/src.txt";
538
const char RELPATH[] = "src.txt";
539
const uint64_t ino = 4;
540
const uint64_t fh = 0xdeadbeefa7ebabe;
541
off_t fsize = 1 << 20; /* 1 MiB */
542
off_t off_in = 1 << 18;
543
off_t off_out = 3 << 17;
544
ssize_t len = 65536;
545
int fd;
546
547
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
548
expect_open(ino, 0, 1, fh);
549
EXPECT_CALL(*m_mock, process(
550
ResultOf([=](auto in) {
551
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
552
in.header.nodeid == ino &&
553
in.body.copy_file_range.fh_in == fh &&
554
(off_t)in.body.copy_file_range.off_in == off_in &&
555
in.body.copy_file_range.nodeid_out == ino &&
556
in.body.copy_file_range.fh_out == fh &&
557
(off_t)in.body.copy_file_range.off_out == off_out &&
558
in.body.copy_file_range.len == (size_t)len &&
559
in.body.copy_file_range.flags == 0);
560
}, Eq(true)),
561
_)
562
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
563
SET_OUT_HEADER_LEN(out, write);
564
out.body.write.size = len;
565
})));
566
567
fd = open(FULLPATH, O_RDWR);
568
ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
569
570
leak(fd);
571
}
572
573
/*
574
* copy_file_range should update the destination's mtime and ctime, and
575
* the source's atime.
576
*/
577
TEST_F(CopyFileRange, timestamps)
578
{
579
const char FULLPATH1[] = "mountpoint/src.txt";
580
const char RELPATH1[] = "src.txt";
581
const char FULLPATH2[] = "mountpoint/dst.txt";
582
const char RELPATH2[] = "dst.txt";
583
struct stat sb1a, sb1b, sb2a, sb2b;
584
const uint64_t ino1 = 42;
585
const uint64_t ino2 = 43;
586
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
587
const uint64_t fh2 = 0xdeadc0de88c0ffee;
588
off_t fsize1 = 1 << 20; /* 1 MiB */
589
off_t fsize2 = 1 << 19; /* 512 KiB */
590
off_t start1 = 1 << 18;
591
off_t start2 = 3 << 17;
592
ssize_t len = 65536;
593
int fd1, fd2;
594
595
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
596
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
597
expect_open(ino1, 0, 1, fh1);
598
expect_open(ino2, 0, 1, fh2);
599
EXPECT_CALL(*m_mock, process(
600
ResultOf([=](auto in) {
601
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
602
in.header.nodeid == ino1 &&
603
in.body.copy_file_range.fh_in == fh1 &&
604
(off_t)in.body.copy_file_range.off_in == start1 &&
605
in.body.copy_file_range.nodeid_out == ino2 &&
606
in.body.copy_file_range.fh_out == fh2 &&
607
(off_t)in.body.copy_file_range.off_out == start2 &&
608
in.body.copy_file_range.len == (size_t)len &&
609
in.body.copy_file_range.flags == 0);
610
}, Eq(true)),
611
_)
612
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
613
SET_OUT_HEADER_LEN(out, write);
614
out.body.write.size = len;
615
})));
616
617
fd1 = open(FULLPATH1, O_RDONLY);
618
ASSERT_GE(fd1, 0);
619
fd2 = open(FULLPATH2, O_WRONLY);
620
ASSERT_GE(fd2, 0);
621
ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
622
ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
623
624
nap();
625
626
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
627
ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
628
ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
629
630
EXPECT_NE(sb1a.st_atime, sb1b.st_atime);
631
EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
632
EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
633
EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
634
EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
635
EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
636
637
leak(fd1);
638
leak(fd2);
639
}
640
641
/*
642
* copy_file_range can extend the size of a file
643
* */
644
TEST_F(CopyFileRange, extend)
645
{
646
const char FULLPATH[] = "mountpoint/src.txt";
647
const char RELPATH[] = "src.txt";
648
struct stat sb;
649
const uint64_t ino = 4;
650
const uint64_t fh = 0xdeadbeefa7ebabe;
651
off_t fsize = 65536;
652
off_t off_in = 0;
653
off_t off_out = 65536;
654
ssize_t len = 65536;
655
int fd;
656
657
expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
658
expect_open(ino, 0, 1, fh);
659
EXPECT_CALL(*m_mock, process(
660
ResultOf([=](auto in) {
661
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
662
in.header.nodeid == ino &&
663
in.body.copy_file_range.fh_in == fh &&
664
(off_t)in.body.copy_file_range.off_in == off_in &&
665
in.body.copy_file_range.nodeid_out == ino &&
666
in.body.copy_file_range.fh_out == fh &&
667
(off_t)in.body.copy_file_range.off_out == off_out &&
668
in.body.copy_file_range.len == (size_t)len &&
669
in.body.copy_file_range.flags == 0);
670
}, Eq(true)),
671
_)
672
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
673
SET_OUT_HEADER_LEN(out, write);
674
out.body.write.size = len;
675
})));
676
677
fd = open(FULLPATH, O_RDWR);
678
ASSERT_GE(fd, 0);
679
ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0));
680
681
/* Check that cached attributes were updated appropriately */
682
ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
683
EXPECT_EQ(fsize + len, sb.st_size);
684
685
leak(fd);
686
}
687
688
/* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */
689
TEST_F(CopyFileRange_7_27, fallback)
690
{
691
const char FULLPATH1[] = "mountpoint/src.txt";
692
const char RELPATH1[] = "src.txt";
693
const char FULLPATH2[] = "mountpoint/dst.txt";
694
const char RELPATH2[] = "dst.txt";
695
const uint64_t ino1 = 42;
696
const uint64_t ino2 = 43;
697
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
698
const uint64_t fh2 = 0xdeadc0de88c0ffee;
699
off_t fsize2 = 0;
700
off_t start1 = 0;
701
off_t start2 = 0;
702
const char *contents = "Hello, world!";
703
ssize_t len;
704
int fd1, fd2;
705
706
len = strlen(contents);
707
708
/*
709
* Ensure that we read to EOF, just so the buffer cache's read size is
710
* predictable.
711
*/
712
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1);
713
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
714
expect_open(ino1, 0, 1, fh1);
715
expect_open(ino2, 0, 1, fh2);
716
EXPECT_CALL(*m_mock, process(
717
ResultOf([=](auto in) {
718
return (in.header.opcode == FUSE_COPY_FILE_RANGE);
719
}, Eq(true)),
720
_)
721
).Times(0);
722
expect_maybe_lseek(ino1);
723
expect_read(ino1, start1, len, len, contents, 0);
724
expect_write(ino2, start2, len, len, contents);
725
726
fd1 = open(FULLPATH1, O_RDONLY);
727
ASSERT_GE(fd1, 0);
728
fd2 = open(FULLPATH2, O_WRONLY);
729
ASSERT_GE(fd2, 0);
730
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
731
732
leak(fd1);
733
leak(fd2);
734
}
735
736
/*
737
* With -o noatime, copy_file_range should update the destination's mtime and
738
* ctime, but not the source's atime.
739
*/
740
TEST_F(CopyFileRangeNoAtime, timestamps)
741
{
742
const char FULLPATH1[] = "mountpoint/src.txt";
743
const char RELPATH1[] = "src.txt";
744
const char FULLPATH2[] = "mountpoint/dst.txt";
745
const char RELPATH2[] = "dst.txt";
746
struct stat sb1a, sb1b, sb2a, sb2b;
747
const uint64_t ino1 = 42;
748
const uint64_t ino2 = 43;
749
const uint64_t fh1 = 0xdeadbeef1a7ebabe;
750
const uint64_t fh2 = 0xdeadc0de88c0ffee;
751
off_t fsize1 = 1 << 20; /* 1 MiB */
752
off_t fsize2 = 1 << 19; /* 512 KiB */
753
off_t start1 = 1 << 18;
754
off_t start2 = 3 << 17;
755
ssize_t len = 65536;
756
int fd1, fd2;
757
758
expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1);
759
expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1);
760
expect_open(ino1, 0, 1, fh1);
761
expect_open(ino2, 0, 1, fh2);
762
EXPECT_CALL(*m_mock, process(
763
ResultOf([=](auto in) {
764
return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
765
in.header.nodeid == ino1 &&
766
in.body.copy_file_range.fh_in == fh1 &&
767
(off_t)in.body.copy_file_range.off_in == start1 &&
768
in.body.copy_file_range.nodeid_out == ino2 &&
769
in.body.copy_file_range.fh_out == fh2 &&
770
(off_t)in.body.copy_file_range.off_out == start2 &&
771
in.body.copy_file_range.len == (size_t)len &&
772
in.body.copy_file_range.flags == 0);
773
}, Eq(true)),
774
_)
775
).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
776
SET_OUT_HEADER_LEN(out, write);
777
out.body.write.size = len;
778
})));
779
780
fd1 = open(FULLPATH1, O_RDONLY);
781
ASSERT_GE(fd1, 0);
782
fd2 = open(FULLPATH2, O_WRONLY);
783
ASSERT_GE(fd2, 0);
784
ASSERT_EQ(0, fstat(fd1, &sb1a)) << strerror(errno);
785
ASSERT_EQ(0, fstat(fd2, &sb2a)) << strerror(errno);
786
787
nap();
788
789
ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0));
790
ASSERT_EQ(0, fstat(fd1, &sb1b)) << strerror(errno);
791
ASSERT_EQ(0, fstat(fd2, &sb2b)) << strerror(errno);
792
793
EXPECT_EQ(sb1a.st_atime, sb1b.st_atime);
794
EXPECT_EQ(sb1a.st_mtime, sb1b.st_mtime);
795
EXPECT_EQ(sb1a.st_ctime, sb1b.st_ctime);
796
EXPECT_EQ(sb2a.st_atime, sb2b.st_atime);
797
EXPECT_NE(sb2a.st_mtime, sb2b.st_mtime);
798
EXPECT_NE(sb2a.st_ctime, sb2b.st_ctime);
799
800
leak(fd1);
801
leak(fd2);
802
}
803
804