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 if ((argc-optind) == 5) { 209 soff = strtoull(argv[optind+2], NULL, 10); 210 if (soff == ULLONG_MAX) { 211 fprintf(stderr, "invalid source offset"); 212 return (1); 213 } 214 doff = strtoull(argv[optind+3], NULL, 10); 215 if (doff == ULLONG_MAX) { 216 fprintf(stderr, "invalid dest offset"); 217 return (1); 218 } 219 if (mode == CF_MODE_COPYFILERANGE && 220 strcmp(argv[optind+4], "all") == 0) { 221 len = SSIZE_MAX; 222 } else { 223 len = strtoull(argv[optind+4], NULL, 10); 224 if (len == ULLONG_MAX) { 225 fprintf(stderr, "invalid length"); 226 return (1); 227 } 228 } 229 } 230 231 int sfd = open(argv[optind], O_RDONLY); 232 if (sfd < 0) { 233 fprintf(stderr, "open: %s: %s\n", 234 argv[optind], strerror(errno)); 235 return (1); 236 } 237 238 int dfd = open(argv[optind+1], O_WRONLY|O_CREAT, 239 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 240 if (dfd < 0) { 241 fprintf(stderr, "open: %s: %s\n", 242 argv[optind+1], strerror(errno)); 243 close(sfd); 244 return (1); 245 } 246 247 int err; 248 switch (mode) { 249 case CF_MODE_CLONE: 250 err = do_clone(sfd, dfd); 251 break; 252 case CF_MODE_CLONERANGE: 253 err = do_clonerange(sfd, dfd, soff, doff, len); 254 break; 255 case CF_MODE_COPYFILERANGE: 256 err = do_copyfilerange(sfd, dfd, soff, doff, len); 257 break; 258 case CF_MODE_DEDUPERANGE: 259 err = do_deduperange(sfd, dfd, soff, doff, len); 260 break; 261 default: 262 abort(); 263 } 264 265 if (!quiet) { 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); 270 271 fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", 272 spos, slen, dpos, dlen); 273 } 274 275 close(dfd); 276 close(sfd); 277 278 return (err == 0 ? 0 : 1); 279 } 280 281 int 282 do_clone(int sfd, int dfd) 283 { 284 if (!quiet) 285 fprintf(stderr, "using FICLONE\n"); 286 int err = ioctl(dfd, CF_FICLONE, sfd); 287 if (err < 0) { 288 fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno)); 289 return (err); 290 } 291 return (0); 292 } 293 294 int 295 do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 296 { 297 if (!quiet) 298 fprintf(stderr, "using FICLONERANGE\n"); 299 cf_file_clone_range_t fcr = { 300 .src_fd = sfd, 301 .src_offset = soff, 302 .src_length = len, 303 .dest_offset = doff, 304 }; 305 int err = ioctl(dfd, CF_FICLONERANGE, &fcr); 306 if (err < 0) { 307 fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno)); 308 return (err); 309 } 310 return (0); 311 } 312 313 int 314 do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 315 { 316 if (!quiet) 317 fprintf(stderr, "using copy_file_range\n"); 318 ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0); 319 if (copied < 0) { 320 fprintf(stderr, "copy_file_range: %s\n", strerror(errno)); 321 return (1); 322 } 323 if (len == SSIZE_MAX) { 324 struct stat sb; 325 326 if (fstat(sfd, &sb) < 0) { 327 fprintf(stderr, "fstat(sfd): %s\n", strerror(errno)); 328 return (1); 329 } 330 len = sb.st_size; 331 } 332 if (copied != len) { 333 fprintf(stderr, "copy_file_range: copied less than requested: " 334 "requested=%lu; copied=%lu\n", len, copied); 335 return (1); 336 } 337 return (0); 338 } 339 340 int 341 do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 342 { 343 if (!quiet) 344 fprintf(stderr, "using FIDEDUPERANGE\n"); 345 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)]; 352 353 fdr->src_offset = soff; 354 fdr->src_length = len; 355 fdr->dest_count = 1; 356 357 fdri->dest_fd = dfd; 358 fdri->dest_offset = doff; 359 360 int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr); 361 if (err != 0) 362 fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno)); 363 364 if (fdri->status < 0) { 365 fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status)); 366 err = -1; 367 } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) { 368 fprintf(stderr, "dedup failed: range differs\n"); 369 err = -1; 370 } 371 372 return (err); 373 } 374