Skip to content

Commit d828c5b

Browse files
committed
zts: block cloning tests
Signed-off-by: Rob Norris <[email protected]> Sponsored-By: OpenDrives Inc. Sponsored-By: Klara Inc.
1 parent da24ea8 commit d828c5b

18 files changed

+1002
-0
lines changed

tests/runfiles/linux.run

Lines changed: 9 additions & 0 deletions
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/zfs-tests/cmd/.gitignore

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 333 additions & 0 deletions
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+
}

tests/zfs-tests/include/commands.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export ZFS_FILES='zdb
182182
export ZFSTEST_FILES='badsend
183183
btree_test
184184
chg_usr_exec
185+
clonefile
185186
devname2devid
186187
dir_rd_update
187188
draid

tests/zfs-tests/tests/Makefile.am

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
9090
functional/alloc_class/alloc_class.kshlib \
9191
functional/atime/atime.cfg \
9292
functional/atime/atime_common.kshlib \
93+
functional/block_cloning/block_cloning.kshlib \
9394
functional/cache/cache.cfg \
9495
functional/cache/cache.kshlib \
9596
functional/cachefile/cachefile.cfg \
@@ -437,6 +438,17 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
437438
functional/atime/root_atime_on.ksh \
438439
functional/atime/root_relatime_on.ksh \
439440
functional/atime/setup.ksh \
441+
functional/block_cloning/cleanup.ksh \
442+
functional/block_cloning/setup.ksh \
443+
functional/block_cloning/block_cloning_copyfilerange_cross_dataset.ksh \
444+
functional/block_cloning/block_cloning_copyfilerange.ksh \
445+
functional/block_cloning/block_cloning_copyfilerange_partial.ksh \
446+
functional/block_cloning/block_cloning_disabled_copyfilerange.ksh \
447+
functional/block_cloning/block_cloning_disabled_ficlone.ksh \
448+
functional/block_cloning/block_cloning_disabled_ficlonerange.ksh \
449+
functional/block_cloning/block_cloning_ficlone.ksh \
450+
functional/block_cloning/block_cloning_ficlonerange.ksh \
451+
functional/block_cloning/block_cloning_ficlonerange_partial.ksh \
440452
functional/bootfs/bootfs_001_pos.ksh \
441453
functional/bootfs/bootfs_002_neg.ksh \
442454
functional/bootfs/bootfs_003_pos.ksh \

0 commit comments

Comments
 (0)