diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index 1cfd5bc6520a..a350966cce99 100644
--- a/fs/xfs/libxfs/xfs_fs.h
+++ b/fs/xfs/libxfs/xfs_fs.h
@@ -831,6 +831,7 @@ struct xfs_scrub_metadata {
 #define XFS_IOC_FSGEOMETRY	     _IOR ('X', 126, struct xfs_fsop_geom)
 #define XFS_IOC_BULKSTAT	     _IOR ('X', 127, struct xfs_bulkstat_req)
 #define XFS_IOC_INUMBERS	     _IOR ('X', 128, struct xfs_inumbers_req)
+#define XFS_IOC_SETFSUUID	     _IOR ('X', 129, uuid_t)
 /*	XFS_IOC_GETFSUUID ---------- deprecated 140	 */
 
 
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 55bb01173cde..f0699a7169e4 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -38,6 +38,7 @@
 #include "xfs_reflink.h"
 #include "xfs_ioctl.h"
 #include "xfs_xattr.h"
+#include "xfs_log.h"
 
 #include <linux/mount.h>
 #include <linux/namei.h>
@@ -1861,6 +1862,109 @@ xfs_fs_eofblocks_from_user(
 	return 0;
 }
 
+static int
+xfs_ioc_setfsuuid(
+	struct file			*filp,
+	struct xfs_mount		*mp,
+	uuid_t				__user *uuid)
+{
+	uuid_t				old_uuid;
+	uuid_t				new_uuid;
+	uuid_t				*forget_uuid = NULL;
+	int				error;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (!xfs_sb_is_v5(&mp->m_sb))
+		return -EOPNOTSUPP;
+
+	if (copy_from_user(&new_uuid, uuid, sizeof(uuid_t)))
+		return -EFAULT;
+	if (uuid_is_null(&new_uuid))
+		return -EINVAL;
+
+	/* Check that the uuid is unique and save a slot in the uuid table. */
+	if (!(xfs_has_nouuid(mp))) {
+		error = xfs_uuid_remember(&new_uuid);
+		if (error)
+			return error;
+		forget_uuid = &new_uuid;
+	}
+
+	error = xfs_internal_freeze(mp);
+	if (error)
+		goto out_drop_uuid;
+
+	spin_lock(&mp->m_sb_lock);
+	uuid_copy(&old_uuid, &mp->m_sb.sb_uuid);
+
+	/*
+	 * On a v5 filesystem, every metadata object has a uuid stamped into
+	 * the header.  The particular uuid used is either sb_uuid or
+	 * sb_meta_uuid, depending on whether the meta_uuid feature is set.
+	 *
+	 * If the meta_uuid feature is set:
+	 * - The user visible uuid is set in sb_uuid
+	 * - The uuid used for metadata blocks is set in sb_meta_uuid
+	 * - If new_uuid == sb_meta_uuid, then we'll deactivate the feature
+	 *   and set sb_uuid to the new uuid
+	 *
+	 * If the meta_uuid feature is not set:
+	 * - The user visible uuid is set in sb_uuid
+	 * - The uuid used for meta blocks should match sb_uuid
+	 * - If new_uuid != sb_uuid, we need to copy sb_uuid to sb_meta_uuid,
+	 *   set the meta_uuid feature bit, and set sb_uuid to the new uuid
+	 */
+	if (xfs_has_metauuid(mp) &&
+	    uuid_equal(&new_uuid, &mp->m_sb.sb_meta_uuid)) {
+		mp->m_sb.sb_features_incompat &= ~XFS_SB_FEAT_INCOMPAT_META_UUID;
+		mp->m_features &= ~XFS_FEAT_META_UUID;
+	} else if (!xfs_has_metauuid(mp) &&
+	    !uuid_equal(&new_uuid, &mp->m_sb.sb_uuid)) {
+		uuid_copy(&mp->m_sb.sb_meta_uuid, &mp->m_sb.sb_uuid);
+		mp->m_sb.sb_features_incompat |= XFS_SB_FEAT_INCOMPAT_META_UUID;
+		mp->m_features |= XFS_FEAT_META_UUID;
+	}
+
+	uuid_copy(&mp->m_sb.sb_uuid, &new_uuid);
+	spin_unlock(&mp->m_sb_lock);
+
+	xlog_iclog_update_uuid(mp);
+
+	xfs_buf_lock(mp->m_sb_bp);
+	xfs_buf_hold(mp->m_sb_bp);
+
+	xfs_sb_to_disk(mp->m_sb_bp->b_addr, &mp->m_sb);
+	error = xfs_bwrite(mp->m_sb_bp);
+	xfs_buf_relse(mp->m_sb_bp);
+	if (error)
+		goto out_drop_freeze;
+
+	/* Update incore state and prepare to drop the old uuid. */
+	uuid_copy(&mp->m_super->s_uuid, &new_uuid);
+	if (!(xfs_has_nouuid(mp)))
+		forget_uuid = &old_uuid;
+
+	/*
+	 * Update the secondary supers, being aware that growfs also updates
+	 * backup supers so we need to lock against that.
+	 */
+	mutex_lock(&mp->m_growlock);
+	error = xfs_update_secondary_sbs(mp);
+	mutex_unlock(&mp->m_growlock);
+
+	invalidate_bdev(mp->m_ddev_targp->bt_bdev);
+	xfs_log_clean(mp);
+
+out_drop_freeze:
+	xfs_internal_unfreeze(mp);
+out_drop_uuid:
+	if (forget_uuid)
+		xfs_uuid_forget(forget_uuid);
+	return error;
+}
+
 /*
  * These long-unused ioctls were removed from the official ioctl API in 5.17,
  * but retain these definitions so that we can log warnings about them.
@@ -2149,6 +2253,9 @@ xfs_file_ioctl(
 		return error;
 	}
 
+	case XFS_IOC_SETFSUUID:
+		return xfs_ioc_setfsuuid(filp, mp, arg);
+
 	default:
 		return -ENOTTY;
 	}
diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c
index fc61cc024023..d79b6065ee9c 100644
--- a/fs/xfs/xfs_log.c
+++ b/fs/xfs/xfs_log.c
@@ -3921,3 +3921,22 @@ xlog_drop_incompat_feat(
 {
 	up_read(&log->l_incompat_users);
 }
+
+/*
+ * Cycle all the iclog buffers and update the uuid.
+ */
+void
+xlog_iclog_update_uuid(
+	struct xfs_mount	*mp)
+{
+	int			i;
+	struct xlog		*log = mp->m_log;
+	struct xlog_in_core	*iclog = log->l_iclog;
+	xlog_rec_header_t	*head;
+
+	for (i = 0; i < log->l_iclog_bufs; i++) {
+		head = &iclog->ic_header;
+		memcpy(&head->h_fs_uuid, &mp->m_sb.sb_uuid, sizeof(uuid_t));
+		iclog = iclog->ic_next;
+	}
+}
diff --git a/fs/xfs/xfs_log.h b/fs/xfs/xfs_log.h
index 2728886c2963..6b607619163e 100644
--- a/fs/xfs/xfs_log.h
+++ b/fs/xfs/xfs_log.h
@@ -163,4 +163,6 @@ void xlog_use_incompat_feat(struct xlog *log);
 void xlog_drop_incompat_feat(struct xlog *log);
 int xfs_attr_use_log_assist(struct xfs_mount *mp);
 
+void xlog_iclog_update_uuid(struct xfs_mount *mp);
+
 #endif	/* __XFS_LOG_H__ */
