diff mbox series

[2/7] libfrog: add support for commit range ioctl family

Message ID 172983773360.3040944.3327362870969316613.stgit@frogsfrogsfrogs (mailing list archive)
State New
Headers show
Series [1/7] man: document file range commit ioctls | expand

Commit Message

Darrick J. Wong Oct. 25, 2024, 6:32 a.m. UTC
From: Darrick J. Wong <djwong@kernel.org>

Add some library code to support the new file range commit ioctls.  This
will be used to test the atomic file commit functionality in fstests.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 libfrog/file_exchange.c |  194 +++++++++++++++++++++++++++++++++++++++++++++++
 libfrog/file_exchange.h |   10 ++
 2 files changed, 204 insertions(+)
diff mbox series

Patch

diff --git a/libfrog/file_exchange.c b/libfrog/file_exchange.c
index 29fdc17e598ce4..e6c3f486b0ffdc 100644
--- a/libfrog/file_exchange.c
+++ b/libfrog/file_exchange.c
@@ -50,3 +50,197 @@  xfrog_exchangerange(
 
 	return 0;
 }
+
+/*
+ * Prepare for committing a file contents exchange if nobody changes file2 in
+ * the meantime by asking the kernel to sample file2's change attributes.
+ *
+ * Returns 0 for success or a negative errno.
+ */
+int
+xfrog_commitrange_prep(
+	struct xfs_commit_range		*xcr,
+	int				file2_fd,
+	off_t				file2_offset,
+	int				file1_fd,
+	off_t				file1_offset,
+	uint64_t			length)
+{
+	int				ret;
+
+	memset(xcr, 0, sizeof(*xcr));
+
+	xcr->file1_fd			= file1_fd;
+	xcr->file1_offset		= file1_offset;
+	xcr->length			= length;
+	xcr->file2_offset		= file2_offset;
+
+	ret = ioctl(file2_fd, XFS_IOC_START_COMMIT, xcr);
+	if (ret)
+		return -errno;
+
+	return 0;
+}
+
+/*
+ * Execute an exchange-commit operation.  Returns 0 for success or a negative
+ * errno.
+ */
+int
+xfrog_commitrange(
+	int				file2_fd,
+	struct xfs_commit_range		*xcr,
+	uint64_t			flags)
+{
+	int				ret;
+
+	xcr->flags = flags;
+
+	ret = ioctl(file2_fd, XFS_IOC_COMMIT_RANGE, xcr);
+	if (ret)
+		return -errno;
+
+	return 0;
+}
+
+/* Opaque freshness blob for XFS_IOC_COMMIT_RANGE */
+struct xfs_commit_range_fresh {
+	xfs_fsid_t	fsid;		/* m_fixedfsid */
+	__u64		file2_ino;	/* inode number */
+	__s64		file2_mtime;	/* modification time */
+	__s64		file2_ctime;	/* change time */
+	__s32		file2_mtime_nsec; /* mod time, nsec */
+	__s32		file2_ctime_nsec; /* change time, nsec */
+	__u32		file2_gen;	/* inode generation */
+	__u32		magic;		/* zero */
+};
+
+/* magic flag to force use of swapext */
+#define XCR_SWAPEXT_MAGIC	0x43524150	/* CRAP */
+
+/*
+ * Import file2 freshness information for a XFS_IOC_SWAPEXT call from bulkstat
+ * information.  We can skip the fsid and file2_gen members because old swapext
+ * did not verify those things.
+ */
+static void
+xfrog_swapext_prep(
+	struct xfs_commit_range		*xdf,
+	const struct xfs_bulkstat	*file2_stat)
+{
+	struct xfs_commit_range_fresh	*f;
+
+	f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
+	f->file2_ino			= file2_stat->bs_ino;
+	f->file2_mtime			= file2_stat->bs_mtime;
+	f->file2_mtime_nsec		= file2_stat->bs_mtime_nsec;
+	f->file2_ctime			= file2_stat->bs_ctime;
+	f->file2_ctime_nsec		= file2_stat->bs_ctime_nsec;
+	f->magic			= XCR_SWAPEXT_MAGIC;
+}
+
+/* Invoke the old swapext ioctl. */
+static int
+xfrog_ioc_swapext(
+	int				file2_fd,
+	struct xfs_commit_range		*xdf)
+{
+	struct xfs_swapext		args = {
+		.sx_version		= XFS_SX_VERSION,
+		.sx_fdtarget		= file2_fd,
+		.sx_length		= xdf->length,
+		.sx_fdtmp		= xdf->file1_fd,
+	};
+	struct xfs_commit_range_fresh	*f;
+	int				ret;
+
+	BUILD_BUG_ON(sizeof(struct xfs_commit_range_fresh) !=
+		     sizeof(xdf->file2_freshness));
+
+	f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
+	args.sx_stat.bs_ino		= f->file2_ino;
+	args.sx_stat.bs_mtime.tv_sec	= f->file2_mtime;
+	args.sx_stat.bs_mtime.tv_nsec	= f->file2_mtime_nsec;
+	args.sx_stat.bs_ctime.tv_sec	= f->file2_ctime;
+	args.sx_stat.bs_ctime.tv_nsec	= f->file2_ctime_nsec;
+
+	ret = ioctl(file2_fd, XFS_IOC_SWAPEXT, &args);
+	if (ret) {
+		/*
+		 * Old swapext returns EFAULT if file1 or file2 length doesn't
+		 * match.  The new new COMMIT_RANGE doesn't check the file
+		 * length, but the freshness checks will trip and return EBUSY.
+		 * If we see EFAULT from the old ioctl, turn that into EBUSY.
+		 */
+		if (errno == EFAULT)
+			return -EBUSY;
+		return -errno;
+	}
+
+	return 0;
+}
+
+/*
+ * Prepare for defragmenting a file by committing a file contents exchange if
+ * if nobody changes file2 in the meantime by asking the kernel to sample
+ * file2's change attributes.
+ *
+ * If the kernel supports only the old XFS_IOC_SWAPEXT ioctl, the @file2_stat
+ * information will be used to sample the change attributes.
+ *
+ * Returns 0 or a negative errno.
+ */
+int
+xfrog_defragrange_prep(
+	struct xfs_commit_range		*xdf,
+	int				file2_fd,
+	const struct xfs_bulkstat	*file2_stat,
+	int				file1_fd)
+{
+	int				ret;
+
+	memset(xdf, 0, sizeof(*xdf));
+
+	xdf->file1_fd			= file1_fd;
+	xdf->length			= file2_stat->bs_size;
+
+	ret = ioctl(file2_fd, XFS_IOC_START_COMMIT, xdf);
+	if (ret && (errno == EOPNOTSUPP || errno == ENOTTY)) {
+		xfrog_swapext_prep(xdf, file2_stat);
+		return 0;
+	}
+	if (ret)
+		return -errno;
+
+	return 0;
+}
+
+/* Execute an exchange operation.  Returns 0 for success or a negative errno. */
+int
+xfrog_defragrange(
+	int				file2_fd,
+	struct xfs_commit_range		*xdf)
+{
+	struct xfs_commit_range_fresh	*f;
+	int				ret;
+
+	f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
+	if (f->magic == XCR_SWAPEXT_MAGIC)
+		goto legacy_fallback;
+
+	ret = ioctl(file2_fd, XFS_IOC_COMMIT_RANGE, xdf);
+	if (ret) {
+		if (errno == EOPNOTSUPP || errno != ENOTTY)
+			goto legacy_fallback;
+		return -errno;
+	}
+
+	return 0;
+
+legacy_fallback:
+	ret = xfrog_ioc_swapext(file2_fd, xdf);
+	if (ret)
+		return -errno;
+
+	return 0;
+}
diff --git a/libfrog/file_exchange.h b/libfrog/file_exchange.h
index b6f6f9f698a8c9..98d3b867c317ee 100644
--- a/libfrog/file_exchange.h
+++ b/libfrog/file_exchange.h
@@ -12,4 +12,14 @@  void xfrog_exchangerange_prep(struct xfs_exchange_range *fxr,
 int xfrog_exchangerange(int file2_fd, struct xfs_exchange_range *fxr,
 		uint64_t flags);
 
+int xfrog_commitrange_prep(struct xfs_commit_range *xcr, int file2_fd,
+		off_t file2_offset, int file1_fd, off_t file1_offset,
+		uint64_t length);
+int xfrog_commitrange(int file2_fd, struct xfs_commit_range *xcr,
+		uint64_t flags);
+
+int xfrog_defragrange_prep(struct xfs_commit_range *xdf, int file2_fd,
+		const struct xfs_bulkstat *file2_stat, int file1_fd);
+int xfrog_defragrange(int file2_fd, struct xfs_commit_range *xdf);
+
 #endif	/* __LIBFROG_FILE_EXCHANGE_H__ */