|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2015 Oracle, Inc. | 
|  | * All Rights Reserved. | 
|  | */ | 
|  |  | 
|  | #include <sys/uio.h> | 
|  | #include <xfs/xfs.h> | 
|  | #include "command.h" | 
|  | #include "input.h" | 
|  | #include "init.h" | 
|  | #include "io.h" | 
|  |  | 
|  | static cmdinfo_t dedupe_cmd; | 
|  | static cmdinfo_t reflink_cmd; | 
|  |  | 
|  | static void | 
|  | dedupe_help(void) | 
|  | { | 
|  | printf(_("\n\ | 
|  | Links a range of bytes (in block size increments) from a file into a range\n\ | 
|  | of bytes in the open file.  The contents of both file ranges must match.\n\ | 
|  | \n\ | 
|  | Example:\n\ | 
|  | 'dedupe some_file 0 4096 32768' - links 32768 bytes from some_file at\n\ | 
|  | offset 0 to into the open file at\n\ | 
|  | position 4096\n\ | 
|  | \n\ | 
|  | Reflink a range of blocks from a given input file to the open file.  Both\n\ | 
|  | files share the same range of physical disk blocks; a write to the shared\n\ | 
|  | range of either file should result in the write landing in a new block and\n\ | 
|  | that range of the file being remapped (i.e. copy-on-write).  Both files\n\ | 
|  | must reside on the same filesystem, and the contents of both ranges must\n\ | 
|  | match.\n\ | 
|  | ")); | 
|  | } | 
|  |  | 
|  | static uint64_t | 
|  | dedupe_ioctl( | 
|  | int		fd, | 
|  | uint64_t	soffset, | 
|  | uint64_t	doffset, | 
|  | uint64_t	len, | 
|  | int		*ops) | 
|  | { | 
|  | struct xfs_extent_data		*args; | 
|  | struct xfs_extent_data_info	*info; | 
|  | int				error; | 
|  | uint64_t			deduped = 0; | 
|  |  | 
|  | args = calloc(1, sizeof(struct xfs_extent_data) + | 
|  | sizeof(struct xfs_extent_data_info)); | 
|  | if (!args) | 
|  | goto done; | 
|  | info = (struct xfs_extent_data_info *)(args + 1); | 
|  | args->logical_offset = soffset; | 
|  | args->length = len; | 
|  | args->dest_count = 1; | 
|  | info->fd = file->fd; | 
|  | info->logical_offset = doffset; | 
|  |  | 
|  | while (args->length > 0 || !*ops) { | 
|  | error = ioctl(fd, XFS_IOC_FILE_EXTENT_SAME, args); | 
|  | if (error) { | 
|  | perror("XFS_IOC_FILE_EXTENT_SAME"); | 
|  | exitcode = 1; | 
|  | goto done; | 
|  | } | 
|  | if (info->status < 0) { | 
|  | fprintf(stderr, "XFS_IOC_FILE_EXTENT_SAME: %s\n", | 
|  | _(strerror(-info->status))); | 
|  | goto done; | 
|  | } | 
|  | if (info->status == XFS_EXTENT_DATA_DIFFERS) { | 
|  | fprintf(stderr, "XFS_IOC_FILE_EXTENT_SAME: %s\n", | 
|  | _("Extents did not match.")); | 
|  | goto done; | 
|  | } | 
|  | if (args->length != 0 && | 
|  | (info->bytes_deduped == 0 || | 
|  | info->bytes_deduped > args->length)) | 
|  | break; | 
|  |  | 
|  | (*ops)++; | 
|  | args->logical_offset += info->bytes_deduped; | 
|  | info->logical_offset += info->bytes_deduped; | 
|  | if (args->length >= info->bytes_deduped) | 
|  | args->length -= info->bytes_deduped; | 
|  | deduped += info->bytes_deduped; | 
|  | } | 
|  | done: | 
|  | free(args); | 
|  | return deduped; | 
|  | } | 
|  |  | 
|  | static int | 
|  | dedupe_f( | 
|  | int		argc, | 
|  | char		**argv) | 
|  | { | 
|  | off64_t		soffset, doffset; | 
|  | long long	count, total; | 
|  | char		*infile; | 
|  | int		condensed, quiet_flag; | 
|  | size_t		fsblocksize, fssectsize; | 
|  | struct timeval	t1, t2; | 
|  | int		c, ops = 0, fd = -1; | 
|  |  | 
|  | condensed = quiet_flag = 0; | 
|  | init_cvtnum(&fsblocksize, &fssectsize); | 
|  |  | 
|  | while ((c = getopt(argc, argv, "Cq")) != EOF) { | 
|  | switch (c) { | 
|  | case 'C': | 
|  | condensed = 1; | 
|  | break; | 
|  | case 'q': | 
|  | quiet_flag = 1; | 
|  | break; | 
|  | default: | 
|  | exitcode = 1; | 
|  | return command_usage(&dedupe_cmd); | 
|  | } | 
|  | } | 
|  | if (optind != argc - 4) { | 
|  | exitcode = 1; | 
|  | return command_usage(&dedupe_cmd); | 
|  | } | 
|  | infile = argv[optind]; | 
|  | optind++; | 
|  | soffset = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (soffset < 0) { | 
|  | printf(_("non-numeric src offset argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | optind++; | 
|  | doffset = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (doffset < 0) { | 
|  | printf(_("non-numeric dest offset argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | optind++; | 
|  | count = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (count < 0) { | 
|  | printf(_("non-positive length argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | fd = openfile(infile, NULL, IO_READONLY, 0, NULL); | 
|  | if (fd < 0) { | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | gettimeofday(&t1, NULL); | 
|  | total = dedupe_ioctl(fd, soffset, doffset, count, &ops); | 
|  | if (ops == 0 || quiet_flag) | 
|  | goto done; | 
|  | gettimeofday(&t2, NULL); | 
|  | t2 = tsub(t2, t1); | 
|  |  | 
|  | report_io_times("deduped", &t2, (long long)doffset, count, total, ops, | 
|  | condensed); | 
|  | done: | 
|  | close(fd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | reflink_help(void) | 
|  | { | 
|  | printf(_("\n\ | 
|  | Links a range of bytes (in block size increments) from a file into a range\n\ | 
|  | of bytes in the open file.  The two extent ranges need not contain identical\n\ | 
|  | data.\n\ | 
|  | \n\ | 
|  | Example:\n\ | 
|  | 'reflink some_file 0 4096 32768' - links 32768 bytes from some_file at\n\ | 
|  | offset 0 to into the open file at\n\ | 
|  | position 4096\n\ | 
|  | 'reflink some_file' - links all bytes from some_file into the open file\n\ | 
|  | at position 0\n\ | 
|  | \n\ | 
|  | Reflink a range of blocks from a given input file to the open file.  Both\n\ | 
|  | files share the same range of physical disk blocks; a write to the shared\n\ | 
|  | range of either file should result in the write landing in a new block and\n\ | 
|  | that range of the file being remapped (i.e. copy-on-write).  Both files\n\ | 
|  | must reside on the same filesystem.\n\ | 
|  | ")); | 
|  | } | 
|  |  | 
|  | static uint64_t | 
|  | reflink_ioctl( | 
|  | int			fd, | 
|  | uint64_t		soffset, | 
|  | uint64_t		doffset, | 
|  | uint64_t		len, | 
|  | int			*ops) | 
|  | { | 
|  | struct xfs_clone_args	args; | 
|  | int			error; | 
|  |  | 
|  | if (soffset == 0 && doffset == 0 && len == 0) { | 
|  | error = ioctl(file->fd, XFS_IOC_CLONE, fd); | 
|  | if (error) | 
|  | perror("XFS_IOC_CLONE"); | 
|  | } else { | 
|  | args.src_fd = fd; | 
|  | args.src_offset = soffset; | 
|  | args.src_length = len; | 
|  | args.dest_offset = doffset; | 
|  | error = ioctl(file->fd, XFS_IOC_CLONE_RANGE, &args); | 
|  | if (error) | 
|  | perror("XFS_IOC_CLONE_RANGE"); | 
|  | } | 
|  | if (!error) | 
|  | (*ops)++; | 
|  | return error ? 0 : len; | 
|  | } | 
|  |  | 
|  | static int | 
|  | reflink_f( | 
|  | int		argc, | 
|  | char		**argv) | 
|  | { | 
|  | off64_t		soffset, doffset; | 
|  | long long	count = 0, total; | 
|  | char		*infile = NULL; | 
|  | int		condensed, quiet_flag; | 
|  | size_t		fsblocksize, fssectsize; | 
|  | struct timeval	t1, t2; | 
|  | int		c, ops = 0, fd = -1; | 
|  |  | 
|  | condensed = quiet_flag = 0; | 
|  | doffset = soffset = 0; | 
|  | init_cvtnum(&fsblocksize, &fssectsize); | 
|  |  | 
|  | while ((c = getopt(argc, argv, "Cq")) != EOF) { | 
|  | switch (c) { | 
|  | case 'C': | 
|  | condensed = 1; | 
|  | break; | 
|  | case 'q': | 
|  | quiet_flag = 1; | 
|  | break; | 
|  | default: | 
|  | exitcode = 1; | 
|  | return command_usage(&reflink_cmd); | 
|  | } | 
|  | } | 
|  | if (optind != argc - 4 && optind != argc - 1) { | 
|  | exitcode = 1; | 
|  | return command_usage(&reflink_cmd); | 
|  | } | 
|  | infile = argv[optind]; | 
|  | optind++; | 
|  | if (optind == argc) | 
|  | goto clone_all; | 
|  | soffset = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (soffset < 0) { | 
|  | printf(_("non-numeric src offset argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | optind++; | 
|  | doffset = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (doffset < 0) { | 
|  | printf(_("non-numeric dest offset argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | optind++; | 
|  | count = cvtnum(fsblocksize, fssectsize, argv[optind]); | 
|  | if (count < 0) { | 
|  | printf(_("non-positive length argument -- %s\n"), argv[optind]); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | clone_all: | 
|  | fd = openfile(infile, NULL, IO_READONLY, 0, NULL); | 
|  | if (fd < 0) { | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | gettimeofday(&t1, NULL); | 
|  | total = reflink_ioctl(fd, soffset, doffset, count, &ops); | 
|  | if (ops == 0) | 
|  | goto done; | 
|  |  | 
|  | if (quiet_flag) | 
|  | goto done; | 
|  | gettimeofday(&t2, NULL); | 
|  | t2 = tsub(t2, t1); | 
|  |  | 
|  | report_io_times("linked", &t2, (long long)doffset, count, total, ops, | 
|  | condensed); | 
|  | done: | 
|  | close(fd); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void | 
|  | reflink_init(void) | 
|  | { | 
|  | reflink_cmd.name = "reflink"; | 
|  | reflink_cmd.altname = "rl"; | 
|  | reflink_cmd.cfunc = reflink_f; | 
|  | reflink_cmd.argmin = 1; | 
|  | reflink_cmd.argmax = -1; | 
|  | reflink_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK | CMD_FLAG_ONESHOT; | 
|  | reflink_cmd.args = | 
|  | _("infile [src_off dst_off len]"); | 
|  | reflink_cmd.oneline = | 
|  | _("reflinks an entire file, or a number of bytes at a specified offset"); | 
|  | reflink_cmd.help = reflink_help; | 
|  |  | 
|  | add_command(&reflink_cmd); | 
|  |  | 
|  | dedupe_cmd.name = "dedupe"; | 
|  | dedupe_cmd.altname = "dd"; | 
|  | dedupe_cmd.cfunc = dedupe_f; | 
|  | dedupe_cmd.argmin = 4; | 
|  | dedupe_cmd.argmax = -1; | 
|  | dedupe_cmd.flags = CMD_NOMAP_OK | CMD_FOREIGN_OK | CMD_FLAG_ONESHOT; | 
|  | dedupe_cmd.args = | 
|  | _("infile src_off dst_off len"); | 
|  | dedupe_cmd.oneline = | 
|  | _("dedupes a number of bytes at a specified offset"); | 
|  | dedupe_cmd.help = dedupe_help; | 
|  |  | 
|  | add_command(&dedupe_cmd); | 
|  | } |