|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | *  Copyright (c) 2016 Netapp, Inc. All rights reserved. | 
|  | */ | 
|  |  | 
|  | #include <sys/syscall.h> | 
|  | #include <sys/uio.h> | 
|  | #include <xfs/xfs.h> | 
|  | #include "command.h" | 
|  | #include "input.h" | 
|  | #include "init.h" | 
|  | #include "io.h" | 
|  |  | 
|  | static cmdinfo_t copy_range_cmd; | 
|  |  | 
|  | static void | 
|  | copy_range_help(void) | 
|  | { | 
|  | printf(_("\n\ | 
|  | Copies a range of bytes from a file into the open file, overwriting any data\n\ | 
|  | already there.\n\ | 
|  | \n\ | 
|  | Example:\n\ | 
|  | 'copy_range -s 100 -d 200 -l 300 some_file' - copies 300 bytes from some_file\n\ | 
|  | at offset 100 into the open\n\ | 
|  | file at offset 200\n\ | 
|  | 'copy_range some_file' - copies all bytes from some_file into the open file\n\ | 
|  | at position 0\n\ | 
|  | 'copy_range -f 2' - copies all bytes from open file 2 into the current open file\n\ | 
|  | at position 0\n\ | 
|  | ")); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Issue a raw copy_file_range syscall; for our test program we don't want the | 
|  | * glibc buffered copy fallback. | 
|  | */ | 
|  | static loff_t | 
|  | copy_file_range_cmd(int fd, long long *src_off, long long *dst_off, size_t len) | 
|  | { | 
|  | loff_t ret; | 
|  |  | 
|  | do { | 
|  | ret = syscall(__NR_copy_file_range, fd, src_off, | 
|  | file->fd, dst_off, len, 0); | 
|  | if (ret == -1) { | 
|  | perror("copy_range"); | 
|  | return errno; | 
|  | } else if (ret == 0) | 
|  | break; | 
|  | len -= ret; | 
|  | } while (len > 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static off_t | 
|  | copy_src_filesize(int fd) | 
|  | { | 
|  | struct stat st; | 
|  |  | 
|  | if (fstat(fd, &st) < 0) { | 
|  | perror("fstat"); | 
|  | return -1; | 
|  | }; | 
|  | return st.st_size; | 
|  | } | 
|  |  | 
|  | static int | 
|  | copy_range_f(int argc, char **argv) | 
|  | { | 
|  | long long src_off = 0; | 
|  | long long dst_off = 0; | 
|  | long long llen; | 
|  | size_t len = 0; | 
|  | bool len_specified = false; | 
|  | int opt; | 
|  | int ret; | 
|  | int fd; | 
|  | int src_path_arg = 1; | 
|  | int src_file_nr = 0; | 
|  | size_t fsblocksize, fssectsize; | 
|  |  | 
|  | init_cvtnum(&fsblocksize, &fssectsize); | 
|  |  | 
|  | while ((opt = getopt(argc, argv, "s:d:l:f:")) != -1) { | 
|  | switch (opt) { | 
|  | case 's': | 
|  | src_off = cvtnum(fsblocksize, fssectsize, optarg); | 
|  | if (src_off < 0) { | 
|  | printf(_("invalid source offset -- %s\n"), optarg); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | break; | 
|  | case 'd': | 
|  | dst_off = cvtnum(fsblocksize, fssectsize, optarg); | 
|  | if (dst_off < 0) { | 
|  | printf(_("invalid destination offset -- %s\n"), optarg); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | break; | 
|  | case 'l': | 
|  | llen = cvtnum(fsblocksize, fssectsize, optarg); | 
|  | if (llen == -1LL) { | 
|  | printf(_("invalid length -- %s\n"), optarg); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | /* | 
|  | * If size_t can't hold what's in llen, report a | 
|  | * length overflow. | 
|  | */ | 
|  | if ((size_t)llen != llen) { | 
|  | errno = EOVERFLOW; | 
|  | perror("copy_range"); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | len = llen; | 
|  | len_specified = true; | 
|  | break; | 
|  | case 'f': | 
|  | src_file_nr = atoi(argv[1]); | 
|  | if (src_file_nr < 0 || src_file_nr >= filecount) { | 
|  | printf(_("file value %d is out of range (0-%d)\n"), | 
|  | src_file_nr, filecount - 1); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | /* Expect no src_path arg */ | 
|  | src_path_arg = 0; | 
|  | break; | 
|  | default: | 
|  | exitcode = 1; | 
|  | return command_usage(©_range_cmd); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (optind != argc - src_path_arg) { | 
|  | exitcode = 1; | 
|  | return command_usage(©_range_cmd); | 
|  | } | 
|  |  | 
|  | if (src_path_arg) { | 
|  | fd = openfile(argv[optind], NULL, IO_READONLY, 0, NULL); | 
|  | if (fd < 0) { | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | } else { | 
|  | fd = filetable[src_file_nr].fd; | 
|  | } | 
|  |  | 
|  | if (!len_specified) { | 
|  | off_t	sz; | 
|  |  | 
|  | sz = copy_src_filesize(fd); | 
|  | if (sz < 0 || (unsigned long long)sz > SIZE_MAX) { | 
|  | ret = 1; | 
|  | goto out; | 
|  | } | 
|  | if (sz > src_off) | 
|  | len = sz - src_off; | 
|  | } | 
|  |  | 
|  | ret = copy_file_range_cmd(fd, &src_off, &dst_off, len); | 
|  | out: | 
|  | close(fd); | 
|  | if (ret < 0) | 
|  | exitcode = 1; | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void | 
|  | copy_range_init(void) | 
|  | { | 
|  | copy_range_cmd.name = "copy_range"; | 
|  | copy_range_cmd.cfunc = copy_range_f; | 
|  | copy_range_cmd.argmin = 1; | 
|  | copy_range_cmd.argmax = 8; | 
|  | copy_range_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK; | 
|  | copy_range_cmd.args = _("[-s src_off] [-d dst_off] [-l len] src_file | -f N"); | 
|  | copy_range_cmd.oneline = _("Copy a range of data between two files"); | 
|  | copy_range_cmd.help = copy_range_help; | 
|  |  | 
|  | add_command(©_range_cmd); | 
|  | } |