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 #ifdef __FreeBSD__ 63 #define loff_t off_t 64 #endif 65 66 ssize_t 67 copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int) 68 __attribute__((weak)); 69 70 static inline ssize_t 71 cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff, 72 size_t len, unsigned int flags) 73 { 74 if (copy_file_range) 75 return (copy_file_range(sfd, soff, dfd, doff, len, flags)); 76 return ( 77 syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags)); 78 } 79 80 /* Define missing FICLONE */ 81 #ifdef FICLONE 82 #define CF_FICLONE FICLONE 83 #else 84 #define CF_FICLONE _IOW(0x94, 9, int) 85 #endif 86 87 /* Define missing FICLONERANGE and support structs */ 88 #ifdef FICLONERANGE 89 #define CF_FICLONERANGE FICLONERANGE 90 typedef struct file_clone_range cf_file_clone_range_t; 91 #else 92 typedef struct { 93 int64_t src_fd; 94 uint64_t src_offset; 95 uint64_t src_length; 96 uint64_t dest_offset; 97 } cf_file_clone_range_t; 98 #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t) 99 #endif 100 101 /* Define missing FIDEDUPERANGE and support structs */ 102 #ifdef FIDEDUPERANGE 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; 108 #else 109 typedef struct { 110 int64_t dest_fd; 111 uint64_t dest_offset; 112 uint64_t bytes_deduped; 113 int32_t status; 114 uint32_t reserved; 115 } cf_file_dedupe_range_info_t; 116 typedef struct { 117 uint64_t src_offset; 118 uint64_t src_length; 119 uint16_t dest_count; 120 uint16_t reserved1; 121 uint32_t reserved2; 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) 127 #endif 128 129 typedef enum { 130 CF_MODE_NONE, 131 CF_MODE_CLONE, 132 CF_MODE_CLONERANGE, 133 CF_MODE_COPYFILERANGE, 134 CF_MODE_DEDUPERANGE, 135 } cf_mode_t; 136 137 static int 138 usage(void) 139 { 140 printf( 141 "usage:\n" 142 " FICLONE:\n" 143 " clonefile -c <src> <dst>\n" 144 " FICLONERANGE:\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" 148 " FIDEDUPERANGE:\n" 149 " clonefile -d <src> <dst> <soff> <doff> <len>\n"); 150 return (1); 151 } 152 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); 157 158 int quiet = 0; 159 160 int 161 main(int argc, char **argv) 162 { 163 cf_mode_t mode = CF_MODE_NONE; 164 165 int c; 166 while ((c = getopt(argc, argv, "crfdq")) != -1) { 167 switch (c) { 168 case 'c': 169 mode = CF_MODE_CLONE; 170 break; 171 case 'r': 172 mode = CF_MODE_CLONERANGE; 173 break; 174 case 'f': 175 mode = CF_MODE_COPYFILERANGE; 176 break; 177 case 'd': 178 mode = CF_MODE_DEDUPERANGE; 179 break; 180 case 'q': 181 quiet = 1; 182 break; 183 } 184 } 185 186 switch (mode) { 187 case CF_MODE_NONE: 188 return (usage()); 189 case CF_MODE_CLONE: 190 if ((argc-optind) != 2) 191 return (usage()); 192 break; 193 case CF_MODE_CLONERANGE: 194 case CF_MODE_DEDUPERANGE: 195 if ((argc-optind) != 5) 196 return (usage()); 197 break; 198 case CF_MODE_COPYFILERANGE: 199 if ((argc-optind) != 2 && (argc-optind) != 5) 200 return (usage()); 201 break; 202 default: 203 abort(); 204 } 205 206 loff_t soff = 0, doff = 0; 207 size_t len = SSIZE_MAX; 208 unsigned long long len2; 209 if ((argc-optind) == 5) { 210 soff = strtoull(argv[optind+2], NULL, 10); 211 if (soff == ULLONG_MAX) { 212 fprintf(stderr, "invalid source offset"); 213 return (1); 214 } 215 doff = strtoull(argv[optind+3], NULL, 10); 216 if (doff == ULLONG_MAX) { 217 fprintf(stderr, "invalid dest offset"); 218 return (1); 219 } 220 if (mode == CF_MODE_COPYFILERANGE && 221 strcmp(argv[optind+4], "all") == 0) { 222 len = SSIZE_MAX; 223 } else { 224 len2 = strtoull(argv[optind+4], NULL, 10); 225 if (len2 == ULLONG_MAX) { 226 fprintf(stderr, "invalid length"); 227 return (1); 228 } 229 if (len2 < SSIZE_MAX) 230 len = (size_t)len2; 231 } 232 } 233 234 int sfd = open(argv[optind], O_RDONLY); 235 if (sfd < 0) { 236 fprintf(stderr, "open: %s: %s\n", 237 argv[optind], strerror(errno)); 238 return (1); 239 } 240 241 int dfd = open(argv[optind+1], O_WRONLY|O_CREAT, 242 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 243 if (dfd < 0) { 244 fprintf(stderr, "open: %s: %s\n", 245 argv[optind+1], strerror(errno)); 246 close(sfd); 247 return (1); 248 } 249 250 int err; 251 switch (mode) { 252 case CF_MODE_CLONE: 253 err = do_clone(sfd, dfd); 254 break; 255 case CF_MODE_CLONERANGE: 256 err = do_clonerange(sfd, dfd, soff, doff, len); 257 break; 258 case CF_MODE_COPYFILERANGE: 259 err = do_copyfilerange(sfd, dfd, soff, doff, len); 260 break; 261 case CF_MODE_DEDUPERANGE: 262 err = do_deduperange(sfd, dfd, soff, doff, len); 263 break; 264 default: 265 abort(); 266 } 267 268 if (!quiet) { 269 off_t spos = lseek(sfd, 0, SEEK_CUR); 270 off_t slen = lseek(sfd, 0, SEEK_END); 271 off_t dpos = lseek(dfd, 0, SEEK_CUR); 272 off_t dlen = lseek(dfd, 0, SEEK_END); 273 274 fprintf(stderr, "file offsets: src=%jd/%jd; dst=%jd/%jd\n", 275 spos, slen, dpos, dlen); 276 } 277 278 close(dfd); 279 close(sfd); 280 281 return (err == 0 ? 0 : 1); 282 } 283 284 int 285 do_clone(int sfd, int dfd) 286 { 287 if (!quiet) 288 fprintf(stderr, "using FICLONE\n"); 289 int err = ioctl(dfd, CF_FICLONE, sfd); 290 if (err < 0) { 291 fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno)); 292 return (err); 293 } 294 return (0); 295 } 296 297 int 298 do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 299 { 300 if (!quiet) 301 fprintf(stderr, "using FICLONERANGE\n"); 302 cf_file_clone_range_t fcr = { 303 .src_fd = sfd, 304 .src_offset = soff, 305 .src_length = len, 306 .dest_offset = doff, 307 }; 308 int err = ioctl(dfd, CF_FICLONERANGE, &fcr); 309 if (err < 0) { 310 fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno)); 311 return (err); 312 } 313 return (0); 314 } 315 316 int 317 do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 318 { 319 if (!quiet) 320 fprintf(stderr, "using copy_file_range\n"); 321 ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0); 322 if (copied < 0) { 323 fprintf(stderr, "copy_file_range: %s\n", strerror(errno)); 324 return (1); 325 } 326 if (len == SSIZE_MAX) { 327 struct stat sb; 328 329 if (fstat(sfd, &sb) < 0) { 330 fprintf(stderr, "fstat(sfd): %s\n", strerror(errno)); 331 return (1); 332 } 333 len = sb.st_size; 334 } 335 if (copied != len) { 336 fprintf(stderr, "copy_file_range: copied less than requested: " 337 "requested=%zu; copied=%zd\n", len, copied); 338 return (1); 339 } 340 return (0); 341 } 342 343 int 344 do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 345 { 346 if (!quiet) 347 fprintf(stderr, "using FIDEDUPERANGE\n"); 348 349 char buf[sizeof (cf_file_dedupe_range_t)+ 350 sizeof (cf_file_dedupe_range_info_t)] = {0}; 351 cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0]; 352 cf_file_dedupe_range_info_t *fdri = 353 (cf_file_dedupe_range_info_t *) 354 &buf[sizeof (cf_file_dedupe_range_t)]; 355 356 fdr->src_offset = soff; 357 fdr->src_length = len; 358 fdr->dest_count = 1; 359 360 fdri->dest_fd = dfd; 361 fdri->dest_offset = doff; 362 363 int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr); 364 if (err != 0) 365 fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno)); 366 367 if (fdri->status < 0) { 368 fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status)); 369 err = -1; 370 } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) { 371 fprintf(stderr, "dedup failed: range differs\n"); 372 err = -1; 373 } 374 375 return (err); 376 } 377