|  | // 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 <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <sys/vfs.h> | 
|  | #include <stdint.h> | 
|  | #include <stdbool.h> | 
|  | #include <fcntl.h> | 
|  | #include <unistd.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  | #include <errno.h> | 
|  | #include <linux/magic.h> | 
|  | #ifdef HAVE_BTRFS_IOCTL_H | 
|  | #include <btrfs/ioctl.h> | 
|  | #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 <cifs/ioctl.h> | 
|  | #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] <src_file> <dest_file>\n" | 
|  | "\tA full file clone is performed by default, " | 
|  | "unless any of the following are specified (Btrfs only):\n" | 
|  | "\t-s <offset>:	source file offset (default = 0)\n" | 
|  | "\t-d <offset>:	destination file offset (default = 0)\n" | 
|  | "\t-l <length>:	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; | 
|  | } |