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 ssize_t 63 copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int) 64 __attribute__((weak)); 65 66 static inline ssize_t 67 cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff, 68 size_t len, unsigned int flags) 69 { 70 if (copy_file_range) 71 return (copy_file_range(sfd, soff, dfd, doff, len, flags)); 72 return ( 73 syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags)); 74 } 75 76 /* Define missing FICLONE */ 77 #ifdef FICLONE 78 #define CF_FICLONE FICLONE 79 #else 80 #define CF_FICLONE _IOW(0x94, 9, int) 81 #endif 82 83 /* Define missing FICLONERANGE and support structs */ 84 #ifdef FICLONERANGE 85 #define CF_FICLONERANGE FICLONERANGE 86 typedef struct file_clone_range cf_file_clone_range_t; 87 #else 88 typedef struct { 89 int64_t src_fd; 90 uint64_t src_offset; 91 uint64_t src_length; 92 uint64_t dest_offset; 93 } cf_file_clone_range_t; 94 #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t) 95 #endif 96 97 /* Define missing FIDEDUPERANGE and support structs */ 98 #ifdef FIDEDUPERANGE 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; 104 #else 105 typedef struct { 106 int64_t dest_fd; 107 uint64_t dest_offset; 108 uint64_t bytes_deduped; 109 int32_t status; 110 uint32_t reserved; 111 } cf_file_dedupe_range_info_t; 112 typedef struct { 113 uint64_t src_offset; 114 uint64_t src_length; 115 uint16_t dest_count; 116 uint16_t reserved1; 117 uint32_t reserved2; 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) 123 #endif 124 125 typedef enum { 126 CF_MODE_NONE, 127 CF_MODE_CLONE, 128 CF_MODE_CLONERANGE, 129 CF_MODE_COPYFILERANGE, 130 CF_MODE_DEDUPERANGE, 131 } cf_mode_t; 132 133 static int 134 usage(void) 135 { 136 printf( 137 "usage:\n" 138 " FICLONE:\n" 139 " clonefile -c <src> <dst>\n" 140 " FICLONERANGE:\n" 141 " clonefile -r <src> <dst> <soff> <doff> <len>\n" 142 " copy_file_range:\n" 143 " clonefile -f <src> <dst> <soff> <doff> <len>\n" 144 " FIDEDUPERANGE:\n" 145 " clonefile -d <src> <dst> <soff> <doff> <len>\n"); 146 return (1); 147 } 148 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); 153 154 int quiet = 0; 155 156 int 157 main(int argc, char **argv) 158 { 159 cf_mode_t mode = CF_MODE_NONE; 160 161 char c; 162 while ((c = getopt(argc, argv, "crfdq")) != -1) { 163 switch (c) { 164 case 'c': 165 mode = CF_MODE_CLONE; 166 break; 167 case 'r': 168 mode = CF_MODE_CLONERANGE; 169 break; 170 case 'f': 171 mode = CF_MODE_COPYFILERANGE; 172 break; 173 case 'd': 174 mode = CF_MODE_DEDUPERANGE; 175 break; 176 case 'q': 177 quiet = 1; 178 break; 179 } 180 } 181 182 if (mode == CF_MODE_NONE || (argc-optind) < 2 || 183 (mode != CF_MODE_CLONE && (argc-optind) < 5)) 184 return (usage()); 185 186 loff_t soff = 0, doff = 0; 187 size_t len = 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"); 192 return (1); 193 } 194 doff = strtoull(argv[optind+3], NULL, 10); 195 if (doff == ULLONG_MAX) { 196 fprintf(stderr, "invalid dest offset"); 197 return (1); 198 } 199 len = strtoull(argv[optind+4], NULL, 10); 200 if (len == ULLONG_MAX) { 201 fprintf(stderr, "invalid length"); 202 return (1); 203 } 204 } 205 206 int sfd = open(argv[optind], O_RDONLY); 207 if (sfd < 0) { 208 fprintf(stderr, "open: %s: %s\n", 209 argv[optind], strerror(errno)); 210 return (1); 211 } 212 213 int dfd = open(argv[optind+1], O_WRONLY|O_CREAT, 214 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 215 if (dfd < 0) { 216 fprintf(stderr, "open: %s: %s\n", 217 argv[optind+1], strerror(errno)); 218 close(sfd); 219 return (1); 220 } 221 222 int err; 223 switch (mode) { 224 case CF_MODE_CLONE: 225 err = do_clone(sfd, dfd); 226 break; 227 case CF_MODE_CLONERANGE: 228 err = do_clonerange(sfd, dfd, soff, doff, len); 229 break; 230 case CF_MODE_COPYFILERANGE: 231 err = do_copyfilerange(sfd, dfd, soff, doff, len); 232 break; 233 case CF_MODE_DEDUPERANGE: 234 err = do_deduperange(sfd, dfd, soff, doff, len); 235 break; 236 default: 237 abort(); 238 } 239 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); 244 245 fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos, slen, 246 dpos, dlen); 247 248 close(dfd); 249 close(sfd); 250 251 return (err == 0 ? 0 : 1); 252 } 253 254 int 255 do_clone(int sfd, int dfd) 256 { 257 fprintf(stderr, "using FICLONE\n"); 258 int err = ioctl(dfd, CF_FICLONE, sfd); 259 if (err < 0) { 260 fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno)); 261 return (err); 262 } 263 return (0); 264 } 265 266 int 267 do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 268 { 269 fprintf(stderr, "using FICLONERANGE\n"); 270 cf_file_clone_range_t fcr = { 271 .src_fd = sfd, 272 .src_offset = soff, 273 .src_length = len, 274 .dest_offset = doff, 275 }; 276 int err = ioctl(dfd, CF_FICLONERANGE, &fcr); 277 if (err < 0) { 278 fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno)); 279 return (err); 280 } 281 return (0); 282 } 283 284 int 285 do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 286 { 287 fprintf(stderr, "using copy_file_range\n"); 288 ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0); 289 if (copied < 0) { 290 fprintf(stderr, "copy_file_range: %s\n", strerror(errno)); 291 return (1); 292 } 293 if (copied != len) { 294 fprintf(stderr, "copy_file_range: copied less than requested: " 295 "requested=%lu; copied=%lu\n", len, copied); 296 return (1); 297 } 298 return (0); 299 } 300 301 int 302 do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len) 303 { 304 fprintf(stderr, "using FIDEDUPERANGE\n"); 305 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)]; 312 313 fdr->src_offset = soff; 314 fdr->src_length = len; 315 fdr->dest_count = 1; 316 317 fdri->dest_fd = dfd; 318 fdri->dest_offset = doff; 319 320 int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr); 321 if (err != 0) 322 fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno)); 323 324 if (fdri->status < 0) { 325 fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status)); 326 err = -1; 327 } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) { 328 fprintf(stderr, "dedup failed: range differs\n"); 329 err = -1; 330 } 331 332 return (err); 333 } 334