1 /*
2  * SPDX-License-Identifier: MIT
3  *
4  * Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
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 (dfd < 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 }
334