--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020-2024 Oracle. All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#include "command.h"
+#include "input.h"
+#include "init.h"
+#include "io.h"
+#include "libfrog/logging.h"
+#include "libfrog/fsgeom.h"
+#include "libfrog/file_exchange.h"
+#include "libfrog/bulkstat.h"
+
+static void
+exchangerange_help(void)
+{
+ printf(_(
+"\n"
+" Exchange file data between the open file descriptor and the supplied filename.\n"
+" -C -- Print timing information in a condensed format\n"
+" -d N -- Start exchanging contents at this position in the open file\n"
+" -f -- Flush changed file data and metadata to disk\n"
+" -l N -- Exchange this many bytes between the two files instead of to EOF\n"
+" -n -- Dry run; do all the parameter validation but do not change anything.\n"
+" -s N -- Start exchanging contents at this position in the supplied file\n"
+" -t -- Print timing information\n"
+" -w -- Only exchange written ranges in the supplied file\n"
+));
+}
+
+static int
+exchangerange_f(
+ int argc,
+ char **argv)
+{
+ struct xfs_exchange_range fxr;
+ struct stat stat;
+ struct timeval t1, t2;
+ uint64_t flags = XFS_EXCHANGE_RANGE_TO_EOF;
+ int64_t src_offset = 0;
+ int64_t dest_offset = 0;
+ int64_t length = -1;
+ size_t fsblocksize, fssectsize;
+ int condensed = 0, quiet_flag = 1;
+ int c;
+ int fd;
+ int ret;
+
+ init_cvtnum(&fsblocksize, &fssectsize);
+ while ((c = getopt(argc, argv, "Ccd:fl:ns:tw")) != -1) {
+ switch (c) {
+ case 'C':
+ condensed = 1;
+ break;
+ case 'd':
+ dest_offset = cvtnum(fsblocksize, fssectsize, optarg);
+ if (dest_offset < 0) {
+ printf(
+ _("non-numeric open file offset argument -- %s\n"),
+ optarg);
+ return 0;
+ }
+ break;
+ case 'f':
+ flags |= XFS_EXCHANGE_RANGE_DSYNC;
+ break;
+ case 'l':
+ length = cvtnum(fsblocksize, fssectsize, optarg);
+ if (length < 0) {
+ printf(
+ _("non-numeric length argument -- %s\n"),
+ optarg);
+ return 0;
+ }
+ flags &= ~XFS_EXCHANGE_RANGE_TO_EOF;
+ break;
+ case 'n':
+ flags |= XFS_EXCHANGE_RANGE_DRY_RUN;
+ break;
+ case 's':
+ src_offset = cvtnum(fsblocksize, fssectsize, optarg);
+ if (src_offset < 0) {
+ printf(
+ _("non-numeric supplied file offset argument -- %s\n"),
+ optarg);
+ return 0;
+ }
+ break;
+ case 't':
+ quiet_flag = 0;
+ break;
+ case 'w':
+ flags |= XFS_EXCHANGE_RANGE_FILE1_WRITTEN;
+ break;
+ default:
+ exchangerange_help();
+ return 0;
+ }
+ }
+ if (optind != argc - 1) {
+ exchangerange_help();
+ return 0;
+ }
+
+ /* open the donor file */
+ fd = openfile(argv[optind], NULL, 0, 0, NULL);
+ if (fd < 0)
+ return 0;
+
+ ret = fstat(file->fd, &stat);
+ if (ret) {
+ perror("fstat");
+ exitcode = 1;
+ goto out;
+ }
+ if (length < 0)
+ length = stat.st_size;
+
+ xfrog_exchangerange_prep(&fxr, dest_offset, fd, src_offset, length);
+ ret = xfrog_exchangerange(file->fd, &fxr, flags);
+ if (ret) {
+ xfrog_perror(ret, "exchangerange");
+ exitcode = 1;
+ goto out;
+ }
+ if (quiet_flag)
+ goto out;
+
+ gettimeofday(&t2, NULL);
+ t2 = tsub(t2, t1);
+
+ report_io_times("exchangerange", &t2, dest_offset, length, length, 1,
+ condensed);
+out:
+ close(fd);
+ return 0;
+}
+
+static struct cmdinfo exchangerange_cmd = {
+ .name = "exchangerange",
+ .cfunc = exchangerange_f,
+ .argmin = 1,
+ .argmax = -1,
+ .flags = CMD_FLAG_ONESHOT | CMD_NOMAP_OK,
+ .help = exchangerange_help,
+};
+
+void
+exchangerange_init(void)
+{
+ exchangerange_cmd.args = _("[-Cfntw] [-d dest_offset] [-s src_offset] [-l length] <donorfile>");
+ exchangerange_cmd.oneline = _("Exchange contents between files.");
+
+ add_command(&exchangerange_cmd);
+}
file is specified by path. Note that file data is not copied (file content moves
with the fork(s)).
.TP
+.BI "exchangerange [OPTIONS]" donor_file "
+Exchanges contents between files.
+The current open file is the target.
+The donor file is specified by path.
+Note that file data is not copied (file content moves with the fork(s)).
+Options include:
+.RS 1.0i
+.PD 0
+.TP 0.4i
+.TP
+.B \-C
+Print timing information in a condensed format.
+.TP
+.BI \-d " dest_offset"
+Swap extents with open file beginning at
+.IR dest_offset .
+.TP
+.B \-f
+Flush changed file data and file metadata to disk.
+.TP
+.BI \-l " length"
+Swap up to
+.I length
+bytes of data.
+.TP
+.B \-n
+Perform all the parameter validation checks but don't change anything.
+.TP
+.BI \-s " src_offset"
+Swap extents with donor file beginning at
+.IR src_offset .
+.TP
+.B \-t
+Print timing information.
+.TP
+.B \-w
+Only exchange written ranges in the supplied file.
+.RE
+.PD
+.TP
.BI "set_encpolicy [ \-c " mode " ] [ \-n " mode " ] [ \-f " flags " ] [ \-s " log2_dusize " ] [ \-v " version " ] [ " keyspec " ]"
On filesystems that support encryption, assign an encryption policy to the
current file.