[PATCH v6 08/13] xfs: reflink CoW-based atomic write support

John Garry
2025-03-13T17:13:54Z

Add reflink support for CoW-based atomic writes.

A new flag - XFS_REFLINK_FORCE_COW - is added to indicate that a
COW fork extent mapping must be returned from xfs_reflink_allocate_cow().

For atomic writes, the idea is that first a CoW fork staging extent is
allocated and then the data is written to the new extent before finally
atomically the mapping is updated.

The semantics are that if XFS_REFLINK_FORCE_COW is set, we will be passed
a CoW fork extent mapping for no error returned.

If XFS_REFLINK_FORCE_COW is set and we find a real extent in the COW fork,
then continue to return that directly, as this would this belong to either
a. the same CoW fork extent which the atomic write previously allocated.
b. a pre-existing real cow extent which is unwritten

A atomic write cow fork extent should not be shared with other inodes,
and will only exist for the lifetime of the atomic write.

Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
fs/xfs/xfs_reflink.c | 18 ++++++++++++++++--
fs/xfs/xfs_reflink.h | 2 ++
2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c
index 674a812ecb4f..690b1eefeb0e 100644
--- a/fs/xfs/xfs_reflink.c
+++ b/fs/xfs/xfs_reflink.c
@@ -466,7 +466,7 @@ xfs_reflink_fill_cow_hole(
*lockmode = XFS_ILOCK_EXCL;

error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found);
- if (error || !*shared)
+ if (error || (!*shared && !(flags & XFS_REFLINK_FORCE_COW)))
goto out_trans_cancel;

if (found) {
@@ -582,9 +582,23 @@ xfs_reflink_allocate_cow(
}

error = xfs_find_trim_cow_extent(ip, imap, cmap, shared, &found);
- if (error || !*shared)
+ if (error)
return error;

+ /*
+ * For no shared data extent, return only as long as
+ * XFS_REFLINK_FORCE_COW is not set.
+ *
+ * For XFS_REFLINK_FORCE_COW set, we always return a COW fork extent
+ * mapping. That would be from either a previously allocated unwritten
+ * COW fork extent, or else a new COW fork extent needs to be
+ * allocated. A previously allocated unwritten COW fork extent could be
+ * from an earlier call with XFS_REFLINK_FORCE_COW set or from a
+ * earlier normal unshare of a data extent.
+ */
+ if (!*shared && !(flags & XFS_REFLINK_FORCE_COW))
+ return 0;
+
/* CoW fork has a real extent */
if (found)
return xfs_reflink_convert_unwritten(ip, imap, cmap,
diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h
index 18f9624017cd..f4115836064b 100644
--- a/fs/xfs/xfs_reflink.h
+++ b/fs/xfs/xfs_reflink.h
@@ -11,6 +11,8 @@
*/
/* convert unwritten extents now */
#define XFS_REFLINK_CONVERT_UNWRITTEN (1u << 0)
+/* force a new COW mapping to be allocated */
+#define XFS_REFLINK_FORCE_COW (1u << 1)

/*
* Check whether it is safe to free COW fork blocks from an inode. It is unsafe
--
2.31.1