1315ee00fSMartin Matuska /* 2315ee00fSMartin Matuska * SPDX-License-Identifier: MIT 3315ee00fSMartin Matuska * 4315ee00fSMartin Matuska * Copyright (c) 2023, Rob Norris <robn@despairlabs.com> 5315ee00fSMartin Matuska * 6315ee00fSMartin Matuska * Permission is hereby granted, free of charge, to any person obtaining a copy 7315ee00fSMartin Matuska * of this software and associated documentation files (the "Software"), to 8315ee00fSMartin Matuska * deal in the Software without restriction, including without limitation the 9315ee00fSMartin Matuska * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10315ee00fSMartin Matuska * sell copies of the Software, and to permit persons to whom the Software is 11315ee00fSMartin Matuska * furnished to do so, subject to the following conditions: 12315ee00fSMartin Matuska * 13315ee00fSMartin Matuska * The above copyright notice and this permission notice shall be included in 14315ee00fSMartin Matuska * all copies or substantial portions of the Software. 15315ee00fSMartin Matuska * 16315ee00fSMartin Matuska * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17315ee00fSMartin Matuska * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18315ee00fSMartin Matuska * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19315ee00fSMartin Matuska * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20315ee00fSMartin Matuska * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21315ee00fSMartin Matuska * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22315ee00fSMartin Matuska * IN THE SOFTWARE. 23315ee00fSMartin Matuska */ 24315ee00fSMartin Matuska 25315ee00fSMartin Matuska /* 26315ee00fSMartin Matuska * This program is to test the availability and behaviour of copy_file_range, 27315ee00fSMartin Matuska * FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should 28315ee00fSMartin Matuska * compile and run even if these features aren't exposed through the libc. 29315ee00fSMartin Matuska */ 30315ee00fSMartin Matuska 31315ee00fSMartin Matuska #include <sys/ioctl.h> 32315ee00fSMartin Matuska #include <sys/types.h> 33315ee00fSMartin Matuska #include <sys/stat.h> 34315ee00fSMartin Matuska #include <fcntl.h> 35315ee00fSMartin Matuska #include <stdint.h> 36315ee00fSMartin Matuska #include <unistd.h> 37315ee00fSMartin Matuska #include <sys/syscall.h> 38315ee00fSMartin Matuska #include <stdlib.h> 39315ee00fSMartin Matuska #include <limits.h> 40315ee00fSMartin Matuska #include <stdio.h> 41315ee00fSMartin Matuska #include <string.h> 42315ee00fSMartin Matuska #include <errno.h> 43315ee00fSMartin Matuska 44315ee00fSMartin Matuska #ifndef __NR_copy_file_range 45315ee00fSMartin Matuska #if defined(__x86_64__) 46315ee00fSMartin Matuska #define __NR_copy_file_range (326) 47315ee00fSMartin Matuska #elif defined(__i386__) 48315ee00fSMartin Matuska #define __NR_copy_file_range (377) 49315ee00fSMartin Matuska #elif defined(__s390__) 50315ee00fSMartin Matuska #define __NR_copy_file_range (375) 51315ee00fSMartin Matuska #elif defined(__arm__) 52315ee00fSMartin Matuska #define __NR_copy_file_range (391) 53315ee00fSMartin Matuska #elif defined(__aarch64__) 54315ee00fSMartin Matuska #define __NR_copy_file_range (285) 55315ee00fSMartin Matuska #elif defined(__powerpc__) 56315ee00fSMartin Matuska #define __NR_copy_file_range (379) 57315ee00fSMartin Matuska #else 58315ee00fSMartin Matuska #error "no definition of __NR_copy_file_range for this platform" 59315ee00fSMartin Matuska #endif 60315ee00fSMartin Matuska #endif /* __NR_copy_file_range */ 61315ee00fSMartin Matuska 62*b356da80SMartin Matuska #ifdef __FreeBSD__ 63*b356da80SMartin Matuska #define loff_t off_t 64*b356da80SMartin Matuska #endif 65*b356da80SMartin Matuska 66315ee00fSMartin Matuska ssize_t 67315ee00fSMartin Matuska copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int) 68315ee00fSMartin Matuska __attribute__((weak)); 69315ee00fSMartin Matuska 70315ee00fSMartin Matuska static inline ssize_t 71315ee00fSMartin Matuska cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff, 72315ee00fSMartin Matuska size_t len, unsigned int flags) 73315ee00fSMartin Matuska { 74315ee00fSMartin Matuska if (copy_file_range) 75315ee00fSMartin Matuska return (copy_file_range(sfd, soff, dfd, doff, len, flags)); 76315ee00fSMartin Matuska return ( 77315ee00fSMartin Matuska syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags)); 78315ee00fSMartin Matuska } 79315ee00fSMartin Matuska 80315ee00fSMartin Matuska /* Define missing FICLONE */ 81315ee00fSMartin Matuska #ifdef FICLONE 82315ee00fSMartin Matuska #define CF_FICLONE FICLONE 83315ee00fSMartin Matuska #else 84315ee00fSMartin Matuska #define CF_FICLONE _IOW(0x94, 9, int) 85315ee00fSMartin Matuska #endif 86315ee00fSMartin Matuska 87315ee00fSMartin Matuska /* Define missing FICLONERANGE and support structs */ 88315ee00fSMartin Matuska #ifdef FICLONERANGE 89315ee00fSMartin Matuska #define CF_FICLONERANGE FICLONERANGE 90315ee00fSMartin Matuska typedef struct file_clone_range cf_file_clone_range_t; 91315ee00fSMartin Matuska #else 92315ee00fSMartin Matuska typedef struct { 93315ee00fSMartin Matuska int64_t src_fd; 94315ee00fSMartin Matuska uint64_t src_offset; 95315ee00fSMartin Matuska uint64_t src_length; 96315ee00fSMartin Matuska uint64_t dest_offset; 97315ee00fSMartin Matuska } cf_file_clone_range_t; 98315ee00fSMartin Matuska #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t) 99315ee00fSMartin Matuska #endif 100315ee00fSMartin Matuska 101315ee00fSMartin Matuska /* Define missing FIDEDUPERANGE and support structs */ 102315ee00fSMartin Matuska #ifdef FIDEDUPERANGE 103315ee00fSMartin Matuska #define CF_FIDEDUPERANGE FIDEDUPERANGE 104315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME 105315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS 106315ee00fSMartin Matuska typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t; 107315ee00fSMartin Matuska typedef struct file_dedupe_range cf_file_dedupe_range_t; 108315ee00fSMartin Matuska #else 109315ee00fSMartin Matuska typedef struct { 110315ee00fSMartin Matuska int64_t dest_fd; 111315ee00fSMartin Matuska uint64_t dest_offset; 112315ee00fSMartin Matuska uint64_t bytes_deduped; 113315ee00fSMartin Matuska int32_t status; 114315ee00fSMartin Matuska uint32_t reserved; 115315ee00fSMartin Matuska } cf_file_dedupe_range_info_t; 116315ee00fSMartin Matuska typedef struct { 117315ee00fSMartin Matuska uint64_t src_offset; 118315ee00fSMartin Matuska uint64_t src_length; 119315ee00fSMartin Matuska uint16_t dest_count; 120315ee00fSMartin Matuska uint16_t reserved1; 121315ee00fSMartin Matuska uint32_t reserved2; 122315ee00fSMartin Matuska cf_file_dedupe_range_info_t info[0]; 123315ee00fSMartin Matuska } cf_file_dedupe_range_t; 124315ee00fSMartin Matuska #define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t) 125315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_SAME (0) 126315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_DIFFERS (1) 127315ee00fSMartin Matuska #endif 128315ee00fSMartin Matuska 129315ee00fSMartin Matuska typedef enum { 130315ee00fSMartin Matuska CF_MODE_NONE, 131315ee00fSMartin Matuska CF_MODE_CLONE, 132315ee00fSMartin Matuska CF_MODE_CLONERANGE, 133315ee00fSMartin Matuska CF_MODE_COPYFILERANGE, 134315ee00fSMartin Matuska CF_MODE_DEDUPERANGE, 135315ee00fSMartin Matuska } cf_mode_t; 136315ee00fSMartin Matuska 137315ee00fSMartin Matuska static int 138315ee00fSMartin Matuska usage(void) 139315ee00fSMartin Matuska { 140315ee00fSMartin Matuska printf( 141315ee00fSMartin Matuska "usage:\n" 142315ee00fSMartin Matuska " FICLONE:\n" 143315ee00fSMartin Matuska " clonefile -c <src> <dst>\n" 144315ee00fSMartin Matuska " FICLONERANGE:\n" 145315ee00fSMartin Matuska " clonefile -r <src> <dst> <soff> <doff> <len>\n" 146315ee00fSMartin Matuska " copy_file_range:\n" 147*b356da80SMartin Matuska " clonefile -f <src> <dst> [<soff> <doff> <len | \"all\">]\n" 148315ee00fSMartin Matuska " FIDEDUPERANGE:\n" 149315ee00fSMartin Matuska " clonefile -d <src> <dst> <soff> <doff> <len>\n"); 150315ee00fSMartin Matuska return (1); 151315ee00fSMartin Matuska } 152315ee00fSMartin Matuska 153315ee00fSMartin Matuska int do_clone(int sfd, int dfd); 154315ee00fSMartin Matuska int do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 155315ee00fSMartin Matuska int do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 156315ee00fSMartin Matuska int do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 157315ee00fSMartin Matuska 158315ee00fSMartin Matuska int quiet = 0; 159315ee00fSMartin Matuska 160315ee00fSMartin Matuska int 161315ee00fSMartin Matuska main(int argc, char **argv) 162315ee00fSMartin Matuska { 163315ee00fSMartin Matuska cf_mode_t mode = CF_MODE_NONE; 164315ee00fSMartin Matuska 165315ee00fSMartin Matuska char c; 166315ee00fSMartin Matuska while ((c = getopt(argc, argv, "crfdq")) != -1) { 167315ee00fSMartin Matuska switch (c) { 168315ee00fSMartin Matuska case 'c': 169315ee00fSMartin Matuska mode = CF_MODE_CLONE; 170315ee00fSMartin Matuska break; 171315ee00fSMartin Matuska case 'r': 172315ee00fSMartin Matuska mode = CF_MODE_CLONERANGE; 173315ee00fSMartin Matuska break; 174315ee00fSMartin Matuska case 'f': 175315ee00fSMartin Matuska mode = CF_MODE_COPYFILERANGE; 176315ee00fSMartin Matuska break; 177315ee00fSMartin Matuska case 'd': 178315ee00fSMartin Matuska mode = CF_MODE_DEDUPERANGE; 179315ee00fSMartin Matuska break; 180315ee00fSMartin Matuska case 'q': 181315ee00fSMartin Matuska quiet = 1; 182315ee00fSMartin Matuska break; 183315ee00fSMartin Matuska } 184315ee00fSMartin Matuska } 185315ee00fSMartin Matuska 186*b356da80SMartin Matuska switch (mode) { 187*b356da80SMartin Matuska case CF_MODE_NONE: 188315ee00fSMartin Matuska return (usage()); 189*b356da80SMartin Matuska case CF_MODE_CLONE: 190*b356da80SMartin Matuska if ((argc-optind) != 2) 191*b356da80SMartin Matuska return (usage()); 192*b356da80SMartin Matuska break; 193*b356da80SMartin Matuska case CF_MODE_CLONERANGE: 194*b356da80SMartin Matuska case CF_MODE_DEDUPERANGE: 195*b356da80SMartin Matuska if ((argc-optind) != 5) 196*b356da80SMartin Matuska return (usage()); 197*b356da80SMartin Matuska break; 198*b356da80SMartin Matuska case CF_MODE_COPYFILERANGE: 199*b356da80SMartin Matuska if ((argc-optind) != 2 && (argc-optind) != 5) 200*b356da80SMartin Matuska return (usage()); 201*b356da80SMartin Matuska break; 202*b356da80SMartin Matuska default: 203*b356da80SMartin Matuska abort(); 204*b356da80SMartin Matuska } 205315ee00fSMartin Matuska 206315ee00fSMartin Matuska loff_t soff = 0, doff = 0; 207*b356da80SMartin Matuska size_t len = SSIZE_MAX; 208*b356da80SMartin Matuska if ((argc-optind) == 5) { 209315ee00fSMartin Matuska soff = strtoull(argv[optind+2], NULL, 10); 210315ee00fSMartin Matuska if (soff == ULLONG_MAX) { 211315ee00fSMartin Matuska fprintf(stderr, "invalid source offset"); 212315ee00fSMartin Matuska return (1); 213315ee00fSMartin Matuska } 214315ee00fSMartin Matuska doff = strtoull(argv[optind+3], NULL, 10); 215315ee00fSMartin Matuska if (doff == ULLONG_MAX) { 216315ee00fSMartin Matuska fprintf(stderr, "invalid dest offset"); 217315ee00fSMartin Matuska return (1); 218315ee00fSMartin Matuska } 219*b356da80SMartin Matuska if (mode == CF_MODE_COPYFILERANGE && 220*b356da80SMartin Matuska strcmp(argv[optind+4], "all") == 0) { 221*b356da80SMartin Matuska len = SSIZE_MAX; 222*b356da80SMartin Matuska } else { 223315ee00fSMartin Matuska len = strtoull(argv[optind+4], NULL, 10); 224315ee00fSMartin Matuska if (len == ULLONG_MAX) { 225315ee00fSMartin Matuska fprintf(stderr, "invalid length"); 226315ee00fSMartin Matuska return (1); 227315ee00fSMartin Matuska } 228315ee00fSMartin Matuska } 229*b356da80SMartin Matuska } 230315ee00fSMartin Matuska 231315ee00fSMartin Matuska int sfd = open(argv[optind], O_RDONLY); 232315ee00fSMartin Matuska if (sfd < 0) { 233315ee00fSMartin Matuska fprintf(stderr, "open: %s: %s\n", 234315ee00fSMartin Matuska argv[optind], strerror(errno)); 235315ee00fSMartin Matuska return (1); 236315ee00fSMartin Matuska } 237315ee00fSMartin Matuska 238315ee00fSMartin Matuska int dfd = open(argv[optind+1], O_WRONLY|O_CREAT, 239315ee00fSMartin Matuska S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 240315ee00fSMartin Matuska if (dfd < 0) { 241315ee00fSMartin Matuska fprintf(stderr, "open: %s: %s\n", 242315ee00fSMartin Matuska argv[optind+1], strerror(errno)); 243315ee00fSMartin Matuska close(sfd); 244315ee00fSMartin Matuska return (1); 245315ee00fSMartin Matuska } 246315ee00fSMartin Matuska 247315ee00fSMartin Matuska int err; 248315ee00fSMartin Matuska switch (mode) { 249315ee00fSMartin Matuska case CF_MODE_CLONE: 250315ee00fSMartin Matuska err = do_clone(sfd, dfd); 251315ee00fSMartin Matuska break; 252315ee00fSMartin Matuska case CF_MODE_CLONERANGE: 253315ee00fSMartin Matuska err = do_clonerange(sfd, dfd, soff, doff, len); 254315ee00fSMartin Matuska break; 255315ee00fSMartin Matuska case CF_MODE_COPYFILERANGE: 256315ee00fSMartin Matuska err = do_copyfilerange(sfd, dfd, soff, doff, len); 257315ee00fSMartin Matuska break; 258315ee00fSMartin Matuska case CF_MODE_DEDUPERANGE: 259315ee00fSMartin Matuska err = do_deduperange(sfd, dfd, soff, doff, len); 260315ee00fSMartin Matuska break; 261315ee00fSMartin Matuska default: 262315ee00fSMartin Matuska abort(); 263315ee00fSMartin Matuska } 264315ee00fSMartin Matuska 265*b356da80SMartin Matuska if (!quiet) { 266315ee00fSMartin Matuska off_t spos = lseek(sfd, 0, SEEK_CUR); 267315ee00fSMartin Matuska off_t slen = lseek(sfd, 0, SEEK_END); 268315ee00fSMartin Matuska off_t dpos = lseek(dfd, 0, SEEK_CUR); 269315ee00fSMartin Matuska off_t dlen = lseek(dfd, 0, SEEK_END); 270315ee00fSMartin Matuska 271*b356da80SMartin Matuska fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", 272*b356da80SMartin Matuska spos, slen, dpos, dlen); 273*b356da80SMartin Matuska } 274315ee00fSMartin Matuska 275315ee00fSMartin Matuska close(dfd); 276315ee00fSMartin Matuska close(sfd); 277315ee00fSMartin Matuska 278315ee00fSMartin Matuska return (err == 0 ? 0 : 1); 279315ee00fSMartin Matuska } 280315ee00fSMartin Matuska 281315ee00fSMartin Matuska int 282315ee00fSMartin Matuska do_clone(int sfd, int dfd) 283315ee00fSMartin Matuska { 284*b356da80SMartin Matuska if (!quiet) 285315ee00fSMartin Matuska fprintf(stderr, "using FICLONE\n"); 286315ee00fSMartin Matuska int err = ioctl(dfd, CF_FICLONE, sfd); 287315ee00fSMartin Matuska if (err < 0) { 288315ee00fSMartin Matuska fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno)); 289315ee00fSMartin Matuska return (err); 290315ee00fSMartin Matuska } 291315ee00fSMartin Matuska return (0); 292315ee00fSMartin Matuska } 293315ee00fSMartin Matuska 294315ee00fSMartin Matuska int 295315ee00fSMartin Matuska do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 296315ee00fSMartin Matuska { 297*b356da80SMartin Matuska if (!quiet) 298315ee00fSMartin Matuska fprintf(stderr, "using FICLONERANGE\n"); 299315ee00fSMartin Matuska cf_file_clone_range_t fcr = { 300315ee00fSMartin Matuska .src_fd = sfd, 301315ee00fSMartin Matuska .src_offset = soff, 302315ee00fSMartin Matuska .src_length = len, 303315ee00fSMartin Matuska .dest_offset = doff, 304315ee00fSMartin Matuska }; 305315ee00fSMartin Matuska int err = ioctl(dfd, CF_FICLONERANGE, &fcr); 306315ee00fSMartin Matuska if (err < 0) { 307315ee00fSMartin Matuska fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno)); 308315ee00fSMartin Matuska return (err); 309315ee00fSMartin Matuska } 310315ee00fSMartin Matuska return (0); 311315ee00fSMartin Matuska } 312315ee00fSMartin Matuska 313315ee00fSMartin Matuska int 314315ee00fSMartin Matuska do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 315315ee00fSMartin Matuska { 316*b356da80SMartin Matuska if (!quiet) 317315ee00fSMartin Matuska fprintf(stderr, "using copy_file_range\n"); 318315ee00fSMartin Matuska ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0); 319315ee00fSMartin Matuska if (copied < 0) { 320315ee00fSMartin Matuska fprintf(stderr, "copy_file_range: %s\n", strerror(errno)); 321315ee00fSMartin Matuska return (1); 322315ee00fSMartin Matuska } 323*b356da80SMartin Matuska if (len == SSIZE_MAX) { 324*b356da80SMartin Matuska struct stat sb; 325*b356da80SMartin Matuska 326*b356da80SMartin Matuska if (fstat(sfd, &sb) < 0) { 327*b356da80SMartin Matuska fprintf(stderr, "fstat(sfd): %s\n", strerror(errno)); 328*b356da80SMartin Matuska return (1); 329*b356da80SMartin Matuska } 330*b356da80SMartin Matuska len = sb.st_size; 331*b356da80SMartin Matuska } 332315ee00fSMartin Matuska if (copied != len) { 333315ee00fSMartin Matuska fprintf(stderr, "copy_file_range: copied less than requested: " 334315ee00fSMartin Matuska "requested=%lu; copied=%lu\n", len, copied); 335315ee00fSMartin Matuska return (1); 336315ee00fSMartin Matuska } 337315ee00fSMartin Matuska return (0); 338315ee00fSMartin Matuska } 339315ee00fSMartin Matuska 340315ee00fSMartin Matuska int 341315ee00fSMartin Matuska do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 342315ee00fSMartin Matuska { 343*b356da80SMartin Matuska if (!quiet) 344315ee00fSMartin Matuska fprintf(stderr, "using FIDEDUPERANGE\n"); 345315ee00fSMartin Matuska 346315ee00fSMartin Matuska char buf[sizeof (cf_file_dedupe_range_t)+ 347315ee00fSMartin Matuska sizeof (cf_file_dedupe_range_info_t)] = {0}; 348315ee00fSMartin Matuska cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0]; 349315ee00fSMartin Matuska cf_file_dedupe_range_info_t *fdri = 350315ee00fSMartin Matuska (cf_file_dedupe_range_info_t *) 351315ee00fSMartin Matuska &buf[sizeof (cf_file_dedupe_range_t)]; 352315ee00fSMartin Matuska 353315ee00fSMartin Matuska fdr->src_offset = soff; 354315ee00fSMartin Matuska fdr->src_length = len; 355315ee00fSMartin Matuska fdr->dest_count = 1; 356315ee00fSMartin Matuska 357315ee00fSMartin Matuska fdri->dest_fd = dfd; 358315ee00fSMartin Matuska fdri->dest_offset = doff; 359315ee00fSMartin Matuska 360315ee00fSMartin Matuska int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr); 361315ee00fSMartin Matuska if (err != 0) 362315ee00fSMartin Matuska fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno)); 363315ee00fSMartin Matuska 364315ee00fSMartin Matuska if (fdri->status < 0) { 365315ee00fSMartin Matuska fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status)); 366315ee00fSMartin Matuska err = -1; 367315ee00fSMartin Matuska } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) { 368315ee00fSMartin Matuska fprintf(stderr, "dedup failed: range differs\n"); 369315ee00fSMartin Matuska err = -1; 370315ee00fSMartin Matuska } 371315ee00fSMartin Matuska 372315ee00fSMartin Matuska return (err); 373315ee00fSMartin Matuska } 374