1*315ee00fSMartin Matuska /* 2*315ee00fSMartin Matuska * SPDX-License-Identifier: MIT 3*315ee00fSMartin Matuska * 4*315ee00fSMartin Matuska * Copyright (c) 2023, Rob Norris <robn@despairlabs.com> 5*315ee00fSMartin Matuska * 6*315ee00fSMartin Matuska * Permission is hereby granted, free of charge, to any person obtaining a copy 7*315ee00fSMartin Matuska * of this software and associated documentation files (the "Software"), to 8*315ee00fSMartin Matuska * deal in the Software without restriction, including without limitation the 9*315ee00fSMartin Matuska * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10*315ee00fSMartin Matuska * sell copies of the Software, and to permit persons to whom the Software is 11*315ee00fSMartin Matuska * furnished to do so, subject to the following conditions: 12*315ee00fSMartin Matuska * 13*315ee00fSMartin Matuska * The above copyright notice and this permission notice shall be included in 14*315ee00fSMartin Matuska * all copies or substantial portions of the Software. 15*315ee00fSMartin Matuska * 16*315ee00fSMartin Matuska * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17*315ee00fSMartin Matuska * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18*315ee00fSMartin Matuska * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19*315ee00fSMartin Matuska * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20*315ee00fSMartin Matuska * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21*315ee00fSMartin Matuska * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22*315ee00fSMartin Matuska * IN THE SOFTWARE. 23*315ee00fSMartin Matuska */ 24*315ee00fSMartin Matuska 25*315ee00fSMartin Matuska /* 26*315ee00fSMartin Matuska * This program is to test the availability and behaviour of copy_file_range, 27*315ee00fSMartin Matuska * FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should 28*315ee00fSMartin Matuska * compile and run even if these features aren't exposed through the libc. 29*315ee00fSMartin Matuska */ 30*315ee00fSMartin Matuska 31*315ee00fSMartin Matuska #include <sys/ioctl.h> 32*315ee00fSMartin Matuska #include <sys/types.h> 33*315ee00fSMartin Matuska #include <sys/stat.h> 34*315ee00fSMartin Matuska #include <fcntl.h> 35*315ee00fSMartin Matuska #include <stdint.h> 36*315ee00fSMartin Matuska #include <unistd.h> 37*315ee00fSMartin Matuska #include <sys/syscall.h> 38*315ee00fSMartin Matuska #include <stdlib.h> 39*315ee00fSMartin Matuska #include <limits.h> 40*315ee00fSMartin Matuska #include <stdio.h> 41*315ee00fSMartin Matuska #include <string.h> 42*315ee00fSMartin Matuska #include <errno.h> 43*315ee00fSMartin Matuska 44*315ee00fSMartin Matuska #ifndef __NR_copy_file_range 45*315ee00fSMartin Matuska #if defined(__x86_64__) 46*315ee00fSMartin Matuska #define __NR_copy_file_range (326) 47*315ee00fSMartin Matuska #elif defined(__i386__) 48*315ee00fSMartin Matuska #define __NR_copy_file_range (377) 49*315ee00fSMartin Matuska #elif defined(__s390__) 50*315ee00fSMartin Matuska #define __NR_copy_file_range (375) 51*315ee00fSMartin Matuska #elif defined(__arm__) 52*315ee00fSMartin Matuska #define __NR_copy_file_range (391) 53*315ee00fSMartin Matuska #elif defined(__aarch64__) 54*315ee00fSMartin Matuska #define __NR_copy_file_range (285) 55*315ee00fSMartin Matuska #elif defined(__powerpc__) 56*315ee00fSMartin Matuska #define __NR_copy_file_range (379) 57*315ee00fSMartin Matuska #else 58*315ee00fSMartin Matuska #error "no definition of __NR_copy_file_range for this platform" 59*315ee00fSMartin Matuska #endif 60*315ee00fSMartin Matuska #endif /* __NR_copy_file_range */ 61*315ee00fSMartin Matuska 62*315ee00fSMartin Matuska ssize_t 63*315ee00fSMartin Matuska copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int) 64*315ee00fSMartin Matuska __attribute__((weak)); 65*315ee00fSMartin Matuska 66*315ee00fSMartin Matuska static inline ssize_t 67*315ee00fSMartin Matuska cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff, 68*315ee00fSMartin Matuska size_t len, unsigned int flags) 69*315ee00fSMartin Matuska { 70*315ee00fSMartin Matuska if (copy_file_range) 71*315ee00fSMartin Matuska return (copy_file_range(sfd, soff, dfd, doff, len, flags)); 72*315ee00fSMartin Matuska return ( 73*315ee00fSMartin Matuska syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags)); 74*315ee00fSMartin Matuska } 75*315ee00fSMartin Matuska 76*315ee00fSMartin Matuska /* Define missing FICLONE */ 77*315ee00fSMartin Matuska #ifdef FICLONE 78*315ee00fSMartin Matuska #define CF_FICLONE FICLONE 79*315ee00fSMartin Matuska #else 80*315ee00fSMartin Matuska #define CF_FICLONE _IOW(0x94, 9, int) 81*315ee00fSMartin Matuska #endif 82*315ee00fSMartin Matuska 83*315ee00fSMartin Matuska /* Define missing FICLONERANGE and support structs */ 84*315ee00fSMartin Matuska #ifdef FICLONERANGE 85*315ee00fSMartin Matuska #define CF_FICLONERANGE FICLONERANGE 86*315ee00fSMartin Matuska typedef struct file_clone_range cf_file_clone_range_t; 87*315ee00fSMartin Matuska #else 88*315ee00fSMartin Matuska typedef struct { 89*315ee00fSMartin Matuska int64_t src_fd; 90*315ee00fSMartin Matuska uint64_t src_offset; 91*315ee00fSMartin Matuska uint64_t src_length; 92*315ee00fSMartin Matuska uint64_t dest_offset; 93*315ee00fSMartin Matuska } cf_file_clone_range_t; 94*315ee00fSMartin Matuska #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t) 95*315ee00fSMartin Matuska #endif 96*315ee00fSMartin Matuska 97*315ee00fSMartin Matuska /* Define missing FIDEDUPERANGE and support structs */ 98*315ee00fSMartin Matuska #ifdef FIDEDUPERANGE 99*315ee00fSMartin Matuska #define CF_FIDEDUPERANGE FIDEDUPERANGE 100*315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME 101*315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS 102*315ee00fSMartin Matuska typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t; 103*315ee00fSMartin Matuska typedef struct file_dedupe_range cf_file_dedupe_range_t; 104*315ee00fSMartin Matuska #else 105*315ee00fSMartin Matuska typedef struct { 106*315ee00fSMartin Matuska int64_t dest_fd; 107*315ee00fSMartin Matuska uint64_t dest_offset; 108*315ee00fSMartin Matuska uint64_t bytes_deduped; 109*315ee00fSMartin Matuska int32_t status; 110*315ee00fSMartin Matuska uint32_t reserved; 111*315ee00fSMartin Matuska } cf_file_dedupe_range_info_t; 112*315ee00fSMartin Matuska typedef struct { 113*315ee00fSMartin Matuska uint64_t src_offset; 114*315ee00fSMartin Matuska uint64_t src_length; 115*315ee00fSMartin Matuska uint16_t dest_count; 116*315ee00fSMartin Matuska uint16_t reserved1; 117*315ee00fSMartin Matuska uint32_t reserved2; 118*315ee00fSMartin Matuska cf_file_dedupe_range_info_t info[0]; 119*315ee00fSMartin Matuska } cf_file_dedupe_range_t; 120*315ee00fSMartin Matuska #define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t) 121*315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_SAME (0) 122*315ee00fSMartin Matuska #define CF_FILE_DEDUPE_RANGE_DIFFERS (1) 123*315ee00fSMartin Matuska #endif 124*315ee00fSMartin Matuska 125*315ee00fSMartin Matuska typedef enum { 126*315ee00fSMartin Matuska CF_MODE_NONE, 127*315ee00fSMartin Matuska CF_MODE_CLONE, 128*315ee00fSMartin Matuska CF_MODE_CLONERANGE, 129*315ee00fSMartin Matuska CF_MODE_COPYFILERANGE, 130*315ee00fSMartin Matuska CF_MODE_DEDUPERANGE, 131*315ee00fSMartin Matuska } cf_mode_t; 132*315ee00fSMartin Matuska 133*315ee00fSMartin Matuska static int 134*315ee00fSMartin Matuska usage(void) 135*315ee00fSMartin Matuska { 136*315ee00fSMartin Matuska printf( 137*315ee00fSMartin Matuska "usage:\n" 138*315ee00fSMartin Matuska " FICLONE:\n" 139*315ee00fSMartin Matuska " clonefile -c <src> <dst>\n" 140*315ee00fSMartin Matuska " FICLONERANGE:\n" 141*315ee00fSMartin Matuska " clonefile -r <src> <dst> <soff> <doff> <len>\n" 142*315ee00fSMartin Matuska " copy_file_range:\n" 143*315ee00fSMartin Matuska " clonefile -f <src> <dst> <soff> <doff> <len>\n" 144*315ee00fSMartin Matuska " FIDEDUPERANGE:\n" 145*315ee00fSMartin Matuska " clonefile -d <src> <dst> <soff> <doff> <len>\n"); 146*315ee00fSMartin Matuska return (1); 147*315ee00fSMartin Matuska } 148*315ee00fSMartin Matuska 149*315ee00fSMartin Matuska int do_clone(int sfd, int dfd); 150*315ee00fSMartin Matuska int do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 151*315ee00fSMartin Matuska int do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 152*315ee00fSMartin Matuska int do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len); 153*315ee00fSMartin Matuska 154*315ee00fSMartin Matuska int quiet = 0; 155*315ee00fSMartin Matuska 156*315ee00fSMartin Matuska int 157*315ee00fSMartin Matuska main(int argc, char **argv) 158*315ee00fSMartin Matuska { 159*315ee00fSMartin Matuska cf_mode_t mode = CF_MODE_NONE; 160*315ee00fSMartin Matuska 161*315ee00fSMartin Matuska char c; 162*315ee00fSMartin Matuska while ((c = getopt(argc, argv, "crfdq")) != -1) { 163*315ee00fSMartin Matuska switch (c) { 164*315ee00fSMartin Matuska case 'c': 165*315ee00fSMartin Matuska mode = CF_MODE_CLONE; 166*315ee00fSMartin Matuska break; 167*315ee00fSMartin Matuska case 'r': 168*315ee00fSMartin Matuska mode = CF_MODE_CLONERANGE; 169*315ee00fSMartin Matuska break; 170*315ee00fSMartin Matuska case 'f': 171*315ee00fSMartin Matuska mode = CF_MODE_COPYFILERANGE; 172*315ee00fSMartin Matuska break; 173*315ee00fSMartin Matuska case 'd': 174*315ee00fSMartin Matuska mode = CF_MODE_DEDUPERANGE; 175*315ee00fSMartin Matuska break; 176*315ee00fSMartin Matuska case 'q': 177*315ee00fSMartin Matuska quiet = 1; 178*315ee00fSMartin Matuska break; 179*315ee00fSMartin Matuska } 180*315ee00fSMartin Matuska } 181*315ee00fSMartin Matuska 182*315ee00fSMartin Matuska if (mode == CF_MODE_NONE || (argc-optind) < 2 || 183*315ee00fSMartin Matuska (mode != CF_MODE_CLONE && (argc-optind) < 5)) 184*315ee00fSMartin Matuska return (usage()); 185*315ee00fSMartin Matuska 186*315ee00fSMartin Matuska loff_t soff = 0, doff = 0; 187*315ee00fSMartin Matuska size_t len = 0; 188*315ee00fSMartin Matuska if (mode != CF_MODE_CLONE) { 189*315ee00fSMartin Matuska soff = strtoull(argv[optind+2], NULL, 10); 190*315ee00fSMartin Matuska if (soff == ULLONG_MAX) { 191*315ee00fSMartin Matuska fprintf(stderr, "invalid source offset"); 192*315ee00fSMartin Matuska return (1); 193*315ee00fSMartin Matuska } 194*315ee00fSMartin Matuska doff = strtoull(argv[optind+3], NULL, 10); 195*315ee00fSMartin Matuska if (doff == ULLONG_MAX) { 196*315ee00fSMartin Matuska fprintf(stderr, "invalid dest offset"); 197*315ee00fSMartin Matuska return (1); 198*315ee00fSMartin Matuska } 199*315ee00fSMartin Matuska len = strtoull(argv[optind+4], NULL, 10); 200*315ee00fSMartin Matuska if (len == ULLONG_MAX) { 201*315ee00fSMartin Matuska fprintf(stderr, "invalid length"); 202*315ee00fSMartin Matuska return (1); 203*315ee00fSMartin Matuska } 204*315ee00fSMartin Matuska } 205*315ee00fSMartin Matuska 206*315ee00fSMartin Matuska int sfd = open(argv[optind], O_RDONLY); 207*315ee00fSMartin Matuska if (sfd < 0) { 208*315ee00fSMartin Matuska fprintf(stderr, "open: %s: %s\n", 209*315ee00fSMartin Matuska argv[optind], strerror(errno)); 210*315ee00fSMartin Matuska return (1); 211*315ee00fSMartin Matuska } 212*315ee00fSMartin Matuska 213*315ee00fSMartin Matuska int dfd = open(argv[optind+1], O_WRONLY|O_CREAT, 214*315ee00fSMartin Matuska S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 215*315ee00fSMartin Matuska if (dfd < 0) { 216*315ee00fSMartin Matuska fprintf(stderr, "open: %s: %s\n", 217*315ee00fSMartin Matuska argv[optind+1], strerror(errno)); 218*315ee00fSMartin Matuska close(sfd); 219*315ee00fSMartin Matuska return (1); 220*315ee00fSMartin Matuska } 221*315ee00fSMartin Matuska 222*315ee00fSMartin Matuska int err; 223*315ee00fSMartin Matuska switch (mode) { 224*315ee00fSMartin Matuska case CF_MODE_CLONE: 225*315ee00fSMartin Matuska err = do_clone(sfd, dfd); 226*315ee00fSMartin Matuska break; 227*315ee00fSMartin Matuska case CF_MODE_CLONERANGE: 228*315ee00fSMartin Matuska err = do_clonerange(sfd, dfd, soff, doff, len); 229*315ee00fSMartin Matuska break; 230*315ee00fSMartin Matuska case CF_MODE_COPYFILERANGE: 231*315ee00fSMartin Matuska err = do_copyfilerange(sfd, dfd, soff, doff, len); 232*315ee00fSMartin Matuska break; 233*315ee00fSMartin Matuska case CF_MODE_DEDUPERANGE: 234*315ee00fSMartin Matuska err = do_deduperange(sfd, dfd, soff, doff, len); 235*315ee00fSMartin Matuska break; 236*315ee00fSMartin Matuska default: 237*315ee00fSMartin Matuska abort(); 238*315ee00fSMartin Matuska } 239*315ee00fSMartin Matuska 240*315ee00fSMartin Matuska off_t spos = lseek(sfd, 0, SEEK_CUR); 241*315ee00fSMartin Matuska off_t slen = lseek(sfd, 0, SEEK_END); 242*315ee00fSMartin Matuska off_t dpos = lseek(dfd, 0, SEEK_CUR); 243*315ee00fSMartin Matuska off_t dlen = lseek(dfd, 0, SEEK_END); 244*315ee00fSMartin Matuska 245*315ee00fSMartin Matuska fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos, slen, 246*315ee00fSMartin Matuska dpos, dlen); 247*315ee00fSMartin Matuska 248*315ee00fSMartin Matuska close(dfd); 249*315ee00fSMartin Matuska close(sfd); 250*315ee00fSMartin Matuska 251*315ee00fSMartin Matuska return (err == 0 ? 0 : 1); 252*315ee00fSMartin Matuska } 253*315ee00fSMartin Matuska 254*315ee00fSMartin Matuska int 255*315ee00fSMartin Matuska do_clone(int sfd, int dfd) 256*315ee00fSMartin Matuska { 257*315ee00fSMartin Matuska fprintf(stderr, "using FICLONE\n"); 258*315ee00fSMartin Matuska int err = ioctl(dfd, CF_FICLONE, sfd); 259*315ee00fSMartin Matuska if (err < 0) { 260*315ee00fSMartin Matuska fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno)); 261*315ee00fSMartin Matuska return (err); 262*315ee00fSMartin Matuska } 263*315ee00fSMartin Matuska return (0); 264*315ee00fSMartin Matuska } 265*315ee00fSMartin Matuska 266*315ee00fSMartin Matuska int 267*315ee00fSMartin Matuska do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 268*315ee00fSMartin Matuska { 269*315ee00fSMartin Matuska fprintf(stderr, "using FICLONERANGE\n"); 270*315ee00fSMartin Matuska cf_file_clone_range_t fcr = { 271*315ee00fSMartin Matuska .src_fd = sfd, 272*315ee00fSMartin Matuska .src_offset = soff, 273*315ee00fSMartin Matuska .src_length = len, 274*315ee00fSMartin Matuska .dest_offset = doff, 275*315ee00fSMartin Matuska }; 276*315ee00fSMartin Matuska int err = ioctl(dfd, CF_FICLONERANGE, &fcr); 277*315ee00fSMartin Matuska if (err < 0) { 278*315ee00fSMartin Matuska fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno)); 279*315ee00fSMartin Matuska return (err); 280*315ee00fSMartin Matuska } 281*315ee00fSMartin Matuska return (0); 282*315ee00fSMartin Matuska } 283*315ee00fSMartin Matuska 284*315ee00fSMartin Matuska int 285*315ee00fSMartin Matuska do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 286*315ee00fSMartin Matuska { 287*315ee00fSMartin Matuska fprintf(stderr, "using copy_file_range\n"); 288*315ee00fSMartin Matuska ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0); 289*315ee00fSMartin Matuska if (copied < 0) { 290*315ee00fSMartin Matuska fprintf(stderr, "copy_file_range: %s\n", strerror(errno)); 291*315ee00fSMartin Matuska return (1); 292*315ee00fSMartin Matuska } 293*315ee00fSMartin Matuska if (copied != len) { 294*315ee00fSMartin Matuska fprintf(stderr, "copy_file_range: copied less than requested: " 295*315ee00fSMartin Matuska "requested=%lu; copied=%lu\n", len, copied); 296*315ee00fSMartin Matuska return (1); 297*315ee00fSMartin Matuska } 298*315ee00fSMartin Matuska return (0); 299*315ee00fSMartin Matuska } 300*315ee00fSMartin Matuska 301*315ee00fSMartin Matuska int 302*315ee00fSMartin Matuska do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 303*315ee00fSMartin Matuska { 304*315ee00fSMartin Matuska fprintf(stderr, "using FIDEDUPERANGE\n"); 305*315ee00fSMartin Matuska 306*315ee00fSMartin Matuska char buf[sizeof (cf_file_dedupe_range_t)+ 307*315ee00fSMartin Matuska sizeof (cf_file_dedupe_range_info_t)] = {0}; 308*315ee00fSMartin Matuska cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0]; 309*315ee00fSMartin Matuska cf_file_dedupe_range_info_t *fdri = 310*315ee00fSMartin Matuska (cf_file_dedupe_range_info_t *) 311*315ee00fSMartin Matuska &buf[sizeof (cf_file_dedupe_range_t)]; 312*315ee00fSMartin Matuska 313*315ee00fSMartin Matuska fdr->src_offset = soff; 314*315ee00fSMartin Matuska fdr->src_length = len; 315*315ee00fSMartin Matuska fdr->dest_count = 1; 316*315ee00fSMartin Matuska 317*315ee00fSMartin Matuska fdri->dest_fd = dfd; 318*315ee00fSMartin Matuska fdri->dest_offset = doff; 319*315ee00fSMartin Matuska 320*315ee00fSMartin Matuska int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr); 321*315ee00fSMartin Matuska if (err != 0) 322*315ee00fSMartin Matuska fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno)); 323*315ee00fSMartin Matuska 324*315ee00fSMartin Matuska if (fdri->status < 0) { 325*315ee00fSMartin Matuska fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status)); 326*315ee00fSMartin Matuska err = -1; 327*315ee00fSMartin Matuska } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) { 328*315ee00fSMartin Matuska fprintf(stderr, "dedup failed: range differs\n"); 329*315ee00fSMartin Matuska err = -1; 330*315ee00fSMartin Matuska } 331*315ee00fSMartin Matuska 332*315ee00fSMartin Matuska return (err); 333*315ee00fSMartin Matuska } 334