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 */
63 copy_file_range(int, loff_t
*, int, loff_t
*, size_t, unsigned int)
64 __attribute__((weak
));
67 cf_copy_file_range(int sfd
, loff_t
*soff
, int dfd
, loff_t
*doff
,
68 size_t len
, unsigned int flags
)
71 return (copy_file_range(sfd
, soff
, dfd
, doff
, len
, flags
));
73 syscall(__NR_copy_file_range
, sfd
, soff
, dfd
, doff
, len
, flags
));
76 /* Define missing FICLONE */
78 #define CF_FICLONE FICLONE
80 #define CF_FICLONE _IOW(0x94, 9, int)
83 /* Define missing FICLONERANGE and support structs */
85 #define CF_FICLONERANGE FICLONERANGE
86 typedef struct file_clone_range cf_file_clone_range_t
;
93 } cf_file_clone_range_t
;
94 #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)
97 /* Define missing FIDEDUPERANGE and support structs */
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
;
107 uint64_t dest_offset
;
108 uint64_t bytes_deduped
;
111 } cf_file_dedupe_range_info_t
;
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)
129 CF_MODE_COPYFILERANGE
,
139 " clonefile -c <src> <dst>\n"
141 " clonefile -r <src> <dst> <soff> <doff> <len>\n"
142 " copy_file_range:\n"
143 " clonefile -f <src> <dst> <soff> <doff> <len>\n"
145 " clonefile -d <src> <dst> <soff> <doff> <len>\n");
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
);
157 main(int argc
, char **argv
)
159 cf_mode_t mode
= CF_MODE_NONE
;
162 while ((c
= getopt(argc
, argv
, "crfdq")) != -1) {
165 mode
= CF_MODE_CLONE
;
168 mode
= CF_MODE_CLONERANGE
;
171 mode
= CF_MODE_COPYFILERANGE
;
174 mode
= CF_MODE_DEDUPERANGE
;
182 if (mode
== CF_MODE_NONE
|| (argc
-optind
) < 2 ||
183 (mode
!= CF_MODE_CLONE
&& (argc
-optind
) < 5))
186 loff_t soff
= 0, doff
= 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");
194 doff
= strtoull(argv
[optind
+3], NULL
, 10);
195 if (doff
== ULLONG_MAX
) {
196 fprintf(stderr
, "invalid dest offset");
199 len
= strtoull(argv
[optind
+4], NULL
, 10);
200 if (len
== ULLONG_MAX
) {
201 fprintf(stderr
, "invalid length");
206 int sfd
= open(argv
[optind
], O_RDONLY
);
208 fprintf(stderr
, "open: %s: %s\n",
209 argv
[optind
], strerror(errno
));
213 int dfd
= open(argv
[optind
+1], O_WRONLY
|O_CREAT
,
214 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IROTH
);
216 fprintf(stderr
, "open: %s: %s\n",
217 argv
[optind
+1], strerror(errno
));
225 err
= do_clone(sfd
, dfd
);
227 case CF_MODE_CLONERANGE
:
228 err
= do_clonerange(sfd
, dfd
, soff
, doff
, len
);
230 case CF_MODE_COPYFILERANGE
:
231 err
= do_copyfilerange(sfd
, dfd
, soff
, doff
, len
);
233 case CF_MODE_DEDUPERANGE
:
234 err
= do_deduperange(sfd
, dfd
, soff
, doff
, len
);
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
);
245 fprintf(stderr
, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos
, slen
,
251 return (err
== 0 ? 0 : 1);
255 do_clone(int sfd
, int dfd
)
257 fprintf(stderr
, "using FICLONE\n");
258 int err
= ioctl(dfd
, CF_FICLONE
, sfd
);
260 fprintf(stderr
, "ioctl(FICLONE): %s\n", strerror(errno
));
267 do_clonerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
269 fprintf(stderr
, "using FICLONERANGE\n");
270 cf_file_clone_range_t fcr
= {
276 int err
= ioctl(dfd
, CF_FICLONERANGE
, &fcr
);
278 fprintf(stderr
, "ioctl(FICLONERANGE): %s\n", strerror(errno
));
285 do_copyfilerange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
287 fprintf(stderr
, "using copy_file_range\n");
288 ssize_t copied
= cf_copy_file_range(sfd
, &soff
, dfd
, &doff
, len
, 0);
290 fprintf(stderr
, "copy_file_range: %s\n", strerror(errno
));
294 fprintf(stderr
, "copy_file_range: copied less than requested: "
295 "requested=%lu; copied=%lu\n", len
, copied
);
302 do_deduperange(int sfd
, int dfd
, loff_t soff
, loff_t doff
, size_t len
)
304 fprintf(stderr
, "using FIDEDUPERANGE\n");
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
)];
313 fdr
->src_offset
= soff
;
314 fdr
->src_length
= len
;
318 fdri
->dest_offset
= doff
;
320 int err
= ioctl(sfd
, CF_FIDEDUPERANGE
, fdr
);
322 fprintf(stderr
, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno
));
324 if (fdri
->status
< 0) {
325 fprintf(stderr
, "dedup failed: %s\n", strerror(-fdri
->status
));
327 } else if (fdri
->status
== CF_FILE_DEDUPE_RANGE_DIFFERS
) {
328 fprintf(stderr
, "dedup failed: range differs\n");