// SPDX-License-Identifier: GPL-2.0+ /* * Tiny program to perform file (range) clones using raw Btrfs and CIFS ioctls. * Copyright (C) 2014 SUSE Linux Products GmbH. All Rights Reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BTRFS_IOCTL_H #include #else struct btrfs_ioctl_clone_range_args { int64_t src_fd; uint64_t src_offset; uint64_t src_length; uint64_t dest_offset; }; #define BTRFS_IOCTL_MAGIC 0x94 #define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) #define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ struct btrfs_ioctl_clone_range_args) #endif #ifdef HAVE_CIFS_IOCTL_H #include #else #define CIFS_IOCTL_MAGIC 0xCF #define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int) #endif #ifndef BTRFS_SUPER_MAGIC #define BTRFS_SUPER_MAGIC 0x9123683E #endif #ifndef CIFS_MAGIC_NUMBER #define CIFS_MAGIC_NUMBER 0xFE534D42 #endif static void usage(char *name, const char *msg) { printf("Fatal: %s\n" "Usage:\n" "%s [options] \n" "\tA full file clone is performed by default, " "unless any of the following are specified (Btrfs only):\n" "\t-s : source file offset (default = 0)\n" "\t-d : destination file offset (default = 0)\n" "\t-l : length of clone (default = 0)\n\n" "\tBoth Btrfs and CIFS are supported. On Btrfs, a COW clone " "is attempted. On CIFS, a server-side copy is requested.\n", msg, name); _exit(1); } static int clone_file_btrfs(int src_fd, int dst_fd) { int ret = ioctl(dst_fd, BTRFS_IOC_CLONE, src_fd); if (ret != 0) ret = errno; return ret; } static int clone_file_cifs(int src_fd, int dst_fd) { int ret = ioctl(dst_fd, CIFS_IOC_COPYCHUNK_FILE, src_fd); if (ret != 0) ret = errno; return ret; } static int clone_file(unsigned int fs_type, int src_fd, int dst_fd) { switch (fs_type) { case BTRFS_SUPER_MAGIC: return clone_file_btrfs(src_fd, dst_fd); break; case CIFS_MAGIC_NUMBER: return clone_file_cifs(src_fd, dst_fd); break; default: return ENOTSUP; break; } } static int clone_file_range_btrfs(int src_fd, int dst_fd, uint64_t src_off, uint64_t dst_off, uint64_t len) { struct btrfs_ioctl_clone_range_args cr_args; int ret; memset(&cr_args, 0, sizeof(cr_args)); cr_args.src_fd = src_fd; cr_args.src_offset = src_off; cr_args.src_length = len; cr_args.dest_offset = dst_off; ret = ioctl(dst_fd, BTRFS_IOC_CLONE_RANGE, &cr_args); if (ret != 0) ret = errno; return ret; } static int clone_file_range(unsigned int fs_type, int src_fd, int dst_fd, uint64_t src_off, uint64_t dst_off, uint64_t len) { switch (fs_type) { case BTRFS_SUPER_MAGIC: return clone_file_range_btrfs(src_fd, dst_fd, src_off, dst_off, len); break; case CIFS_MAGIC_NUMBER: /* only supports full file server-side copies */ default: return ENOTSUP; break; } } static int cloner_check_fs_support(int src_fd, int dest_fd, unsigned int *fs_type) { int ret; struct statfs sfs; ret = fstatfs(src_fd, &sfs); if (ret != 0) { printf("failed to stat source FS\n"); return errno; } if ((sfs.f_type != BTRFS_SUPER_MAGIC) && (sfs.f_type != CIFS_MAGIC_NUMBER)) { printf("unsupported source FS 0x%x\n", (unsigned int)sfs.f_type); return ENOTSUP; } *fs_type = (unsigned int)sfs.f_type; ret = fstatfs(dest_fd, &sfs); if (ret != 0) { printf("failed to stat destination FS\n"); return errno; } if (sfs.f_type != *fs_type) { printf("dest FS type 0x%x does not match source 0x%x\n", (unsigned int)sfs.f_type, *fs_type); return ENOTSUP; } return 0; } int main(int argc, char **argv) { bool full_file = true; uint64_t src_off = 0; uint64_t dst_off = 0; uint64_t len = 0; char *src_file; int src_fd; char *dst_file; int dst_fd; int ret; int opt; unsigned int fs_type = 0; while ((opt = getopt(argc, argv, "s:d:l:")) != -1) { char *sval_end; switch (opt) { case 's': errno = 0; src_off = strtoull(optarg, &sval_end, 10); if ((errno) || (*sval_end != '\0')) usage(argv[0], "invalid source offset"); full_file = false; break; case 'd': errno = 0; dst_off = strtoull(optarg, &sval_end, 10); if ((errno) || (*sval_end != '\0')) usage(argv[0], "invalid destination offset"); full_file = false; break; case 'l': errno = 0; len = strtoull(optarg, &sval_end, 10); if ((errno) || (*sval_end != '\0')) usage(argv[0], "invalid length"); full_file = false; break; default: usage(argv[0], "invalid argument"); } } /* should be exactly two args left */ if (optind != argc - 2) usage(argv[0], "src_file and dst_file arguments are madatory"); src_file = (char *)strdup(argv[optind++]); if (src_file == NULL) { ret = ENOMEM; printf("no memory\n"); goto err_out; } dst_file = (char *)strdup(argv[optind++]); if (dst_file == NULL) { ret = ENOMEM; printf("no memory\n"); goto err_src_free; } src_fd = open(src_file, O_RDONLY); if (src_fd == -1) { ret = errno; printf("failed to open %s: %s\n", src_file, strerror(errno)); goto err_dst_free; } dst_fd = open(dst_file, O_CREAT | O_WRONLY, 0644); if (dst_fd == -1) { ret = errno; printf("failed to open %s: %s\n", dst_file, strerror(errno)); goto err_src_close; } ret = cloner_check_fs_support(src_fd, dst_fd, &fs_type); if (ret != 0) { goto err_dst_close; } if (full_file) { ret = clone_file(fs_type, src_fd, dst_fd); } else { ret = clone_file_range(fs_type, src_fd, dst_fd, src_off, dst_off, len); } if (ret != 0) { printf("clone failed: %s\n", strerror(ret)); goto err_dst_close; } ret = 0; err_dst_close: if (close(dst_fd)) { ret |= errno; printf("failed to close dst file: %s\n", strerror(errno)); } err_src_close: if (close(src_fd)) { ret |= errno; printf("failed to close src file: %s\n", strerror(errno)); } err_dst_free: free(dst_file); err_src_free: free(src_file); err_out: return ret; }