| // 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 off64_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) { | 
 | 		off64_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); | 
 | } |