diff --git a/io/Makefile b/io/Makefile
index 6725936d..2f9249e8 100644
--- a/io/Makefile
+++ b/io/Makefile
@@ -11,7 +11,8 @@ HFILES = init.h io.h
 CFILES = init.c \
 	attr.c bmap.c cowextsize.c encrypt.c file.c freeze.c fsync.c \
 	getrusage.c imap.c link.c mmap.c open.c parent.c pread.c prealloc.c \
-	pwrite.c reflink.c seek.c shutdown.c stat.c sync.c truncate.c utimes.c
+	pwrite.c reflink.c seek.c shutdown.c stat.c swapext.c sync.c \
+	truncate.c utimes.c
 
 LLDLIBS = $(LIBXCMD) $(LIBHANDLE) $(LIBPTHREAD)
 LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE)
diff --git a/io/init.c b/io/init.c
index 20d5f80d..2ade03f4 100644
--- a/io/init.c
+++ b/io/init.c
@@ -88,6 +88,7 @@ init_commands(void)
 	sendfile_init();
 	shutdown_init();
 	stat_init();
+	swapext_init();
 	sync_init();
 	sync_range_init();
 	truncate_init();
diff --git a/io/io.h b/io/io.h
index 3862985f..258311f1 100644
--- a/io/io.h
+++ b/io/io.h
@@ -118,6 +118,7 @@ extern void		quit_init(void);
 extern void		seek_init(void);
 extern void		shutdown_init(void);
 extern void		stat_init(void);
+extern void		swapext_init(void);
 extern void		sync_init(void);
 extern void		truncate_init(void);
 extern void		utimes_init(void);
diff --git a/io/swapext.c b/io/swapext.c
new file mode 100644
index 00000000..5e161d69
--- /dev/null
+++ b/io/swapext.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018 Red Hat, Inc.
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "command.h"
+#include "input.h"
+#include "init.h"
+#include "io.h"
+
+static cmdinfo_t swapext_cmd;
+
+static void
+swapext_help(void)
+{
+	printf(_(
+"\n"
+" Swaps extents between the open file descriptor and the supplied filename.\n"
+"\n"));
+}
+
+static int
+xfs_bulkstat_single(
+	int			fd,
+	xfs_ino_t		*lastip,
+	struct xfs_bstat	*ubuffer)
+{
+	struct xfs_fsop_bulkreq	bulkreq;
+
+	bulkreq.lastip = (__u64 *)lastip;
+	bulkreq.icount = 1;
+	bulkreq.ubuffer = ubuffer;
+	bulkreq.ocount = NULL;
+	return ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq);
+}
+
+static int
+swapext_f(
+	int			argc,
+	char			**argv)
+{
+	int			fd;
+	int			error;
+	struct xfs_swapext	sx;
+	struct stat		stat;
+
+	/* open the donor file */
+	fd = openfile(argv[1], NULL, 0, 0, NULL);
+	if (fd < 0)
+		return 0;
+
+	/*
+	 * stat the target file to get the inode number and use the latter to
+	 * get the bulkstat info for the swapext cmd.
+	 */
+	error = fstat(file->fd, &stat);
+	if (error) {
+		perror("fstat");
+		goto out;
+	}
+
+	error = xfs_bulkstat_single(file->fd, &stat.st_ino, &sx.sx_stat);
+	if (error) {
+		perror("bulkstat");
+		goto out;
+	}
+	sx.sx_version = XFS_SX_VERSION;
+	sx.sx_fdtarget = file->fd;
+	sx.sx_fdtmp = fd;
+	sx.sx_offset = 0;
+	sx.sx_length = stat.st_size;
+	error = ioctl(file->fd, XFS_IOC_SWAPEXT, &sx);
+	if (error)
+		perror("swapext");
+
+out:
+	close(fd);
+	return 0;
+}
+
+void
+swapext_init(void)
+{
+	swapext_cmd.name = "swapext";
+	swapext_cmd.cfunc = swapext_f;
+	swapext_cmd.argmin = 1;
+	swapext_cmd.argmax = 1;
+	swapext_cmd.flags = CMD_NOMAP_OK;
+	swapext_cmd.args = _("<donorfile>");
+	swapext_cmd.oneline = _("Swap extents between files.");
+	swapext_cmd.help = swapext_help;
+
+	add_command(&swapext_cmd);
+}
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index 9bf1a478..1e40aa64 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -761,6 +761,11 @@ sec uses UNIX timestamp notation and is the seconds elapsed since
 nsec is the nanoseconds since the sec. This value needs to be in
 the range 0-999999999 with UTIME_NOW and UTIME_OMIT being exceptions.
 Each (sec, nsec) pair constitutes a single timestamp value.
+.TP
+.BI swapext " donor_file "
+Swaps extent forks 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)).
 
 .SH MEMORY MAPPED I/O COMMANDS
 .TP
