Skip to content

Commit 48d0e94

Browse files
robnbehlendorf
authored andcommitted
zts: block cloning tests
Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Kay Pedersen <[email protected]> Signed-off-by: Rob Norris <[email protected]> Sponsored-By: OpenDrives Inc. Sponsored-By: Klara Inc. Closes #15050 Closes CTSRD-CHERI#405 Closes #13349
1 parent 6b0a4be commit 48d0e94

19 files changed

+1016
-0
lines changed

tests/runfiles/linux.run

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ tags = ['functional', 'acl', 'posix-sa']
3434
tests = ['atime_003_pos', 'root_relatime_on']
3535
tags = ['functional', 'atime']
3636

37+
[tests/functional/block_cloning:Linux]
38+
tests = ['block_cloning_copyfilerange', 'block_cloning_copyfilerange_partial',
39+
'block_cloning_ficlone', 'block_cloning_ficlonerange',
40+
'block_cloning_ficlonerange_partial',
41+
'block_cloning_disabled_copyfilerange', 'block_cloning_disabled_ficlone',
42+
'block_cloning_disabled_ficlonerange',
43+
'block_cloning_copyfilerange_cross_dataset']
44+
tags = ['functional', 'block_cloning']
45+
3746
[tests/functional/chattr:Linux]
3847
tests = ['chattr_001_pos', 'chattr_002_neg']
3948
tags = ['functional', 'chattr']

tests/test-runner/bin/zts-report.py.in

+14
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ ci_reason = 'CI runner doesn\'t have all requirements'
134134
#
135135
idmap_reason = 'Idmapped mount needs kernel 5.12+'
136136

137+
#
138+
# copy_file_range() is not supported by all kernels
139+
#
140+
cfr_reason = 'Kernel copy_file_range support required'
141+
cfr_cross_reason = 'copy_file_range(2) cross-filesystem needs kernel 5.3+'
142+
137143
#
138144
# These tests are known to fail, thus we use this list to prevent these
139145
# failures from failing the job as a whole; only unexpected failures
@@ -288,6 +294,14 @@ elif sys.platform.startswith('linux'):
288294
'idmap_mount/idmap_mount_003': ['SKIP', idmap_reason],
289295
'idmap_mount/idmap_mount_004': ['SKIP', idmap_reason],
290296
'idmap_mount/idmap_mount_005': ['SKIP', idmap_reason],
297+
'block_cloning/block_cloning_disabled_copyfilerange':
298+
['SKIP', cfr_reason],
299+
'block_cloning/block_cloning_copyfilerange':
300+
['SKIP', cfr_reason],
301+
'block_cloning/block_cloning_copyfilerange_partial':
302+
['SKIP', cfr_reason],
303+
'block_cloning/block_cloning_copyfilerange_cross_dataset':
304+
['SKIP', cfr_cross_reason],
291305
})
292306

293307

tests/zfs-tests/cmd/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/badsend
22
/btree_test
33
/chg_usr_exec
4+
/clonefile
45
/devname2devid
56
/dir_rd_update
67
/draid

tests/zfs-tests/cmd/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/renameat2
119119
scripts_zfs_tests_bin_PROGRAMS += %D%/xattrtest
120120
scripts_zfs_tests_bin_PROGRAMS += %D%/zed_fd_spill-zedlet
121121
scripts_zfs_tests_bin_PROGRAMS += %D%/idmap_util
122+
scripts_zfs_tests_bin_PROGRAMS += %D%/clonefile
122123

123124
%C%_idmap_util_LDADD = libspl.la
124125

tests/zfs-tests/cmd/clonefile.c

+333
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
/*
2+
* SPDX-License-Identifier: MIT
3+
*
4+
* Copyright (c) 2023, Rob Norris <[email protected]>
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to
8+
* deal in the Software without restriction, including without limitation the
9+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10+
* sell copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22+
* IN THE SOFTWARE.
23+
*/
24+
25+
/*
26+
* This program is to test the availability and behaviour of copy_file_range,
27+
* FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should
28+
* compile and run even if these features aren't exposed through the libc.
29+
*/
30+
31+
#include <sys/ioctl.h>
32+
#include <sys/types.h>
33+
#include <sys/stat.h>
34+
#include <fcntl.h>
35+
#include <stdint.h>
36+
#include <unistd.h>
37+
#include <sys/syscall.h>
38+
#include <stdlib.h>
39+
#include <limits.h>
40+
#include <stdio.h>
41+
#include <string.h>
42+
#include <errno.h>
43+
44+
#ifndef __NR_copy_file_range
45+
#if defined(__x86_64__)
46+
#define __NR_copy_file_range (326)
47+
#elif defined(__i386__)
48+
#define __NR_copy_file_range (377)
49+
#elif defined(__s390__)
50+
#define __NR_copy_file_range (375)
51+
#elif defined(__arm__)
52+
#define __NR_copy_file_range (391)
53+
#elif defined(__aarch64__)
54+
#define __NR_copy_file_range (285)
55+
#elif defined(__powerpc__)
56+
#define __NR_copy_file_range (379)
57+
#else
58+
#error "no definition of __NR_copy_file_range for this platform"
59+
#endif
60+
#endif /* __NR_copy_file_range */
61+
62+
ssize_t
63+
copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int)
64+
__attribute__((weak));
65+
66+
static inline ssize_t
67+
cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff,
68+
size_t len, unsigned int flags)
69+
{
70+
if (copy_file_range)
71+
return (copy_file_range(sfd, soff, dfd, doff, len, flags));
72+
return (
73+
syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags));
74+
}
75+
76+
/* Define missing FICLONE */
77+
#ifdef FICLONE
78+
#define CF_FICLONE FICLONE
79+
#else
80+
#define CF_FICLONE _IOW(0x94, 9, int)
81+
#endif
82+
83+
/* Define missing FICLONERANGE and support structs */
84+
#ifdef FICLONERANGE
85+
#define CF_FICLONERANGE FICLONERANGE
86+
typedef struct file_clone_range cf_file_clone_range_t;
87+
#else
88+
typedef struct {
89+
int64_t src_fd;
90+
uint64_t src_offset;
91+
uint64_t src_length;
92+
uint64_t dest_offset;
93+
} cf_file_clone_range_t;
94+
#define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)
95+
#endif
96+
97+
/* Define missing FIDEDUPERANGE and support structs */
98+
#ifdef FIDEDUPERANGE
99+
#define CF_FIDEDUPERANGE FIDEDUPERANGE
100+
#define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME
101+
#define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS
102+
typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t;
103+
typedef struct file_dedupe_range cf_file_dedupe_range_t;
104+
#else
105+
typedef struct {
106+
int64_t dest_fd;
107+
uint64_t dest_offset;
108+
uint64_t bytes_deduped;
109+
int32_t status;
110+
uint32_t reserved;
111+
} cf_file_dedupe_range_info_t;
112+
typedef struct {
113+
uint64_t src_offset;
114+
uint64_t src_length;
115+
uint16_t dest_count;
116+
uint16_t reserved1;
117+
uint32_t reserved2;
118+
cf_file_dedupe_range_info_t info[0];
119+
} cf_file_dedupe_range_t;
120+
#define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t)
121+
#define CF_FILE_DEDUPE_RANGE_SAME (0)
122+
#define CF_FILE_DEDUPE_RANGE_DIFFERS (1)
123+
#endif
124+
125+
typedef enum {
126+
CF_MODE_NONE,
127+
CF_MODE_CLONE,
128+
CF_MODE_CLONERANGE,
129+
CF_MODE_COPYFILERANGE,
130+
CF_MODE_DEDUPERANGE,
131+
} cf_mode_t;
132+
133+
static int
134+
usage(void)
135+
{
136+
printf(
137+
"usage:\n"
138+
" FICLONE:\n"
139+
" clonefile -c <src> <dst>\n"
140+
" FICLONERANGE:\n"
141+
" clonefile -r <src> <dst> <soff> <doff> <len>\n"
142+
" copy_file_range:\n"
143+
" clonefile -f <src> <dst> <soff> <doff> <len>\n"
144+
" FIDEDUPERANGE:\n"
145+
" clonefile -d <src> <dst> <soff> <doff> <len>\n");
146+
return (1);
147+
}
148+
149+
int do_clone(int sfd, int dfd);
150+
int do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
151+
int do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
152+
int do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
153+
154+
int quiet = 0;
155+
156+
int
157+
main(int argc, char **argv)
158+
{
159+
cf_mode_t mode = CF_MODE_NONE;
160+
161+
char c;
162+
while ((c = getopt(argc, argv, "crfdq")) != -1) {
163+
switch (c) {
164+
case 'c':
165+
mode = CF_MODE_CLONE;
166+
break;
167+
case 'r':
168+
mode = CF_MODE_CLONERANGE;
169+
break;
170+
case 'f':
171+
mode = CF_MODE_COPYFILERANGE;
172+
break;
173+
case 'd':
174+
mode = CF_MODE_DEDUPERANGE;
175+
break;
176+
case 'q':
177+
quiet = 1;
178+
break;
179+
}
180+
}
181+
182+
if (mode == CF_MODE_NONE || (argc-optind) < 2 ||
183+
(mode != CF_MODE_CLONE && (argc-optind) < 5))
184+
return (usage());
185+
186+
loff_t soff = 0, doff = 0;
187+
size_t len = 0;
188+
if (mode != CF_MODE_CLONE) {
189+
soff = strtoull(argv[optind+2], NULL, 10);
190+
if (soff == ULLONG_MAX) {
191+
fprintf(stderr, "invalid source offset");
192+
return (1);
193+
}
194+
doff = strtoull(argv[optind+3], NULL, 10);
195+
if (doff == ULLONG_MAX) {
196+
fprintf(stderr, "invalid dest offset");
197+
return (1);
198+
}
199+
len = strtoull(argv[optind+4], NULL, 10);
200+
if (len == ULLONG_MAX) {
201+
fprintf(stderr, "invalid length");
202+
return (1);
203+
}
204+
}
205+
206+
int sfd = open(argv[optind], O_RDONLY);
207+
if (sfd < 0) {
208+
fprintf(stderr, "open: %s: %s\n",
209+
argv[optind], strerror(errno));
210+
return (1);
211+
}
212+
213+
int dfd = open(argv[optind+1], O_WRONLY|O_CREAT,
214+
S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
215+
if (sfd < 0) {
216+
fprintf(stderr, "open: %s: %s\n",
217+
argv[optind+1], strerror(errno));
218+
close(sfd);
219+
return (1);
220+
}
221+
222+
int err;
223+
switch (mode) {
224+
case CF_MODE_CLONE:
225+
err = do_clone(sfd, dfd);
226+
break;
227+
case CF_MODE_CLONERANGE:
228+
err = do_clonerange(sfd, dfd, soff, doff, len);
229+
break;
230+
case CF_MODE_COPYFILERANGE:
231+
err = do_copyfilerange(sfd, dfd, soff, doff, len);
232+
break;
233+
case CF_MODE_DEDUPERANGE:
234+
err = do_deduperange(sfd, dfd, soff, doff, len);
235+
break;
236+
default:
237+
abort();
238+
}
239+
240+
off_t spos = lseek(sfd, 0, SEEK_CUR);
241+
off_t slen = lseek(sfd, 0, SEEK_END);
242+
off_t dpos = lseek(dfd, 0, SEEK_CUR);
243+
off_t dlen = lseek(dfd, 0, SEEK_END);
244+
245+
fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos, slen,
246+
dpos, dlen);
247+
248+
close(dfd);
249+
close(sfd);
250+
251+
return (err == 0 ? 0 : 1);
252+
}
253+
254+
int
255+
do_clone(int sfd, int dfd)
256+
{
257+
fprintf(stderr, "using FICLONE\n");
258+
int err = ioctl(dfd, CF_FICLONE, sfd);
259+
if (err < 0) {
260+
fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno));
261+
return (err);
262+
}
263+
return (0);
264+
}
265+
266+
int
267+
do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
268+
{
269+
fprintf(stderr, "using FICLONERANGE\n");
270+
cf_file_clone_range_t fcr = {
271+
.src_fd = sfd,
272+
.src_offset = soff,
273+
.src_length = len,
274+
.dest_offset = doff,
275+
};
276+
int err = ioctl(dfd, CF_FICLONERANGE, &fcr);
277+
if (err < 0) {
278+
fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno));
279+
return (err);
280+
}
281+
return (0);
282+
}
283+
284+
int
285+
do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
286+
{
287+
fprintf(stderr, "using copy_file_range\n");
288+
ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0);
289+
if (copied < 0) {
290+
fprintf(stderr, "copy_file_range: %s\n", strerror(errno));
291+
return (1);
292+
}
293+
if (copied != len) {
294+
fprintf(stderr, "copy_file_range: copied less than requested: "
295+
"requested=%lu; copied=%lu\n", len, copied);
296+
return (1);
297+
}
298+
return (0);
299+
}
300+
301+
int
302+
do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
303+
{
304+
fprintf(stderr, "using FIDEDUPERANGE\n");
305+
306+
char buf[sizeof (cf_file_dedupe_range_t)+
307+
sizeof (cf_file_dedupe_range_info_t)] = {0};
308+
cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0];
309+
cf_file_dedupe_range_info_t *fdri =
310+
(cf_file_dedupe_range_info_t *)
311+
&buf[sizeof (cf_file_dedupe_range_t)];
312+
313+
fdr->src_offset = soff;
314+
fdr->src_length = len;
315+
fdr->dest_count = 1;
316+
317+
fdri->dest_fd = dfd;
318+
fdri->dest_offset = doff;
319+
320+
int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr);
321+
if (err != 0)
322+
fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno));
323+
324+
if (fdri->status < 0) {
325+
fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status));
326+
err = -1;
327+
} else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) {
328+
fprintf(stderr, "dedup failed: range differs\n");
329+
err = -1;
330+
}
331+
332+
return (err);
333+
}

0 commit comments

Comments
 (0)