2 * SPDX-License-Identifier: MIT
4 * Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
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.
31 #include <sys/ioctl.h>
32 #include <sys/types.h>
37 #include <sys/syscall.h>
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)
58 #error "no definition of __NR_copy_file_range for this platform"
60 #endif /* __NR_copy_file_range */
67 copy_file_range(int, loff_t
*, int, loff_t
*, size_t, unsigned int)
68 __attribute__((weak
));
71 cf_copy_file_range(int sfd
, loff_t
*soff
, int dfd
, loff_t
*doff
,
72 size_t len
, unsigned int flags
)
75 return (copy_file_range(sfd
, soff
, dfd
, doff
, len
, flags
));
77 syscall(__NR_copy_file_range
, sfd
, soff
, dfd
, doff
, len
, flags
));
80 /* Define missing FICLONE */
82 #define CF_FICLONE FICLONE
84 #define CF_FICLONE _IOW(0x94, 9, int)
87 /* Define missing FICLONERANGE and support structs */
89 #define CF_FICLONERANGE FICLONERANGE
90 typedef struct file_clone_range cf_file_clone_range_t
;
97 } cf_file_clone_range_t
;
98 #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)
101 /* Define missing FIDEDUPERANGE and support structs */
103 #define CF_FIDEDUPERANGE FIDEDUPERANGE
104 #define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME
105 #define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS
106 typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t
;
107 typedef struct file_dedupe_range cf_file_dedupe_range_t
;
111 uint64_t dest_offset
;
112 uint64_t bytes_deduped
;
115 } cf_file_dedupe_range_info_t
;
122 cf_file_dedupe_range_info_t info
[0];
123 } cf_file_dedupe_range_t
;
124 #define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t)
125 #define CF_FILE_DEDUPE_RANGE_SAME (0)
126 #define CF_FILE_DEDUPE_RANGE_DIFFERS (1)
133 CF_MODE_COPYFILERANGE
,
143 " clonefile -c <src> <dst>\n"
145 " clonefile -r <src> <dst> <soff> <doff> <len>\n"
146 " copy_file_range:\n"
147 " clonefile -f <src> <dst> [<soff> <doff> <len | \"all\">]\n"
149 " clonefile -d <src> <dst> <soff> <doff> <len>\n");
153 int do_clone(int sfd
, int dfd
);
154 int do_clonerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
);
155 int do_copyfilerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
);
156 int do_deduperange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
);
161 main(int argc
, char **argv
)
163 cf_mode_t mode
= CF_MODE_NONE
;
166 while ((c
= getopt(argc
, argv
, "crfdq")) != -1) {
169 mode
= CF_MODE_CLONE
;
172 mode
= CF_MODE_CLONERANGE
;
175 mode
= CF_MODE_COPYFILERANGE
;
178 mode
= CF_MODE_DEDUPERANGE
;
190 if ((argc
-optind
) != 2)
193 case CF_MODE_CLONERANGE
:
194 case CF_MODE_DEDUPERANGE
:
195 if ((argc
-optind
) != 5)
198 case CF_MODE_COPYFILERANGE
:
199 if ((argc
-optind
) != 2 && (argc
-optind
) != 5)
206 loff_t soff
= 0, doff
= 0;
207 size_t len
= SSIZE_MAX
;
208 if ((argc
-optind
) == 5) {
209 soff
= strtoull(argv
[optind
+2], NULL
, 10);
210 if (soff
== ULLONG_MAX
) {
211 fprintf(stderr
, "invalid source offset");
214 doff
= strtoull(argv
[optind
+3], NULL
, 10);
215 if (doff
== ULLONG_MAX
) {
216 fprintf(stderr
, "invalid dest offset");
219 if (mode
== CF_MODE_COPYFILERANGE
&&
220 strcmp(argv
[optind
+4], "all") == 0) {
223 len
= strtoull(argv
[optind
+4], NULL
, 10);
224 if (len
== ULLONG_MAX
) {
225 fprintf(stderr
, "invalid length");
231 int sfd
= open(argv
[optind
], O_RDONLY
);
233 fprintf(stderr
, "open: %s: %s\n",
234 argv
[optind
], strerror(errno
));
238 int dfd
= open(argv
[optind
+1], O_WRONLY
|O_CREAT
,
239 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
);
241 fprintf(stderr
, "open: %s: %s\n",
242 argv
[optind
+1], strerror(errno
));
250 err
= do_clone(sfd
, dfd
);
252 case CF_MODE_CLONERANGE
:
253 err
= do_clonerange(sfd
, dfd
, soff
, doff
, len
);
255 case CF_MODE_COPYFILERANGE
:
256 err
= do_copyfilerange(sfd
, dfd
, soff
, doff
, len
);
258 case CF_MODE_DEDUPERANGE
:
259 err
= do_deduperange(sfd
, dfd
, soff
, doff
, len
);
266 off_t spos
= lseek(sfd
, 0, SEEK_CUR
);
267 off_t slen
= lseek(sfd
, 0, SEEK_END
);
268 off_t dpos
= lseek(dfd
, 0, SEEK_CUR
);
269 off_t dlen
= lseek(dfd
, 0, SEEK_END
);
271 fprintf(stderr
, "file offsets: src=%lu/%lu; dst=%lu/%lu\n",
272 spos
, slen
, dpos
, dlen
);
278 return (err
== 0 ? 0 : 1);
282 do_clone(int sfd
, int dfd
)
285 fprintf(stderr
, "using FICLONE\n");
286 int err
= ioctl(dfd
, CF_FICLONE
, sfd
);
288 fprintf(stderr
, "ioctl(FICLONE): %s\n", strerror(errno
));
295 do_clonerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
298 fprintf(stderr
, "using FICLONERANGE\n");
299 cf_file_clone_range_t fcr
= {
305 int err
= ioctl(dfd
, CF_FICLONERANGE
, &fcr
);
307 fprintf(stderr
, "ioctl(FICLONERANGE): %s\n", strerror(errno
));
314 do_copyfilerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
317 fprintf(stderr
, "using copy_file_range\n");
318 ssize_t copied
= cf_copy_file_range(sfd
, &soff
, dfd
, &doff
, len
, 0);
320 fprintf(stderr
, "copy_file_range: %s\n", strerror(errno
));
323 if (len
== SSIZE_MAX
) {
326 if (fstat(sfd
, &sb
) < 0) {
327 fprintf(stderr
, "fstat(sfd): %s\n", strerror(errno
));
333 fprintf(stderr
, "copy_file_range: copied less than requested: "
334 "requested=%lu; copied=%lu\n", len
, copied
);
341 do_deduperange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
344 fprintf(stderr
, "using FIDEDUPERANGE\n");
346 char buf
[sizeof (cf_file_dedupe_range_t
)+
347 sizeof (cf_file_dedupe_range_info_t
)] = {0};
348 cf_file_dedupe_range_t
*fdr
= (cf_file_dedupe_range_t
*)&buf
[0];
349 cf_file_dedupe_range_info_t
*fdri
=
350 (cf_file_dedupe_range_info_t
*)
351 &buf
[sizeof (cf_file_dedupe_range_t
)];
353 fdr
->src_offset
= soff
;
354 fdr
->src_length
= len
;
358 fdri
->dest_offset
= doff
;
360 int err
= ioctl(sfd
, CF_FIDEDUPERANGE
, fdr
);
362 fprintf(stderr
, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno
));
364 if (fdri
->status
< 0) {
365 fprintf(stderr
, "dedup failed: %s\n", strerror(-fdri
->status
));
367 } else if (fdri
->status
== CF_FILE_DEDUPE_RANGE_DIFFERS
) {
368 fprintf(stderr
, "dedup failed: range differs\n");