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