xfs: create delalloc extents in CoW fork
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 3 Oct 2016 16:11:33 +0000 (09:11 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Wed, 5 Oct 2016 01:06:40 +0000 (18:06 -0700)
Wire up iomap_begin to detect shared extents and create delayed allocation
extents in the CoW fork:

 1) Check if we already have an extent in the COW fork for the area.
    If so nothing to do, we can move along.
 2) Look up block number for the current extent, and if there is none
    it's not shared move along.
 3) Unshare the current extent as far as we are going to write into it.
    For this we avoid an additional COW fork lookup and use the
    information we set aside in step 1) above.
 4) Goto 1) unless we've covered the whole range.

Last but not least, this updates the xfs_reflink_reserve_cow_range calling
convention to pass a byte offset and length, as that is what both callers
expect anyway.  This patch has been refactored considerably as part of the
iomap transition.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
fs/xfs/xfs_iomap.c
fs/xfs/xfs_reflink.c
fs/xfs/xfs_reflink.h

index 59c7beb48ef676a0ef3c846776e73cfd877b016c..e8312b0c90246e456e5edd8a0d7ce57b88fb89f9 100644 (file)
@@ -39,6 +39,7 @@
 #include "xfs_quota.h"
 #include "xfs_dquot_item.h"
 #include "xfs_dquot.h"
+#include "xfs_reflink.h"
 
 
 #define XFS_WRITEIO_ALIGN(mp,off)      (((off) >> mp->m_writeio_log) \
@@ -961,8 +962,15 @@ xfs_file_iomap_begin(
        if (XFS_FORCED_SHUTDOWN(mp))
                return -EIO;
 
-       if ((flags & IOMAP_WRITE) &&
-           !IS_DAX(inode) && !xfs_get_extsz_hint(ip)) {
+       if ((flags & (IOMAP_WRITE | IOMAP_ZERO)) && xfs_is_reflink_inode(ip)) {
+               error = xfs_reflink_reserve_cow_range(ip, offset, length);
+               if (error < 0)
+                       return error;
+       }
+
+       if ((flags & IOMAP_WRITE) && !IS_DAX(inode) &&
+                  !xfs_get_extsz_hint(ip)) {
+               /* Reserve delalloc blocks for regular writeback. */
                return xfs_file_iomap_begin_delay(inode, offset, length, flags,
                                iomap);
        }
index 7adbb83df040c9b660a8a81d8486ed89cb443ea9..5d796b7f23d7bdd027365dcca3644b72af62fba1 100644 (file)
@@ -51,6 +51,7 @@
 #include "xfs_btree.h"
 #include "xfs_bmap_btree.h"
 #include "xfs_reflink.h"
+#include "xfs_iomap.h"
 
 /*
  * Copy on Write of Shared Blocks
  * ioend structure.  Better yet, the more ground we can cover with one
  * ioend, the better.
  */
+
+/*
+ * Given an AG extent, find the lowest-numbered run of shared blocks
+ * within that range and return the range in fbno/flen.  If
+ * find_end_of_shared is true, return the longest contiguous extent of
+ * shared blocks.  If there are no shared extents, fbno and flen will
+ * be set to NULLAGBLOCK and 0, respectively.
+ */
+int
+xfs_reflink_find_shared(
+       struct xfs_mount        *mp,
+       xfs_agnumber_t          agno,
+       xfs_agblock_t           agbno,
+       xfs_extlen_t            aglen,
+       xfs_agblock_t           *fbno,
+       xfs_extlen_t            *flen,
+       bool                    find_end_of_shared)
+{
+       struct xfs_buf          *agbp;
+       struct xfs_btree_cur    *cur;
+       int                     error;
+
+       error = xfs_alloc_read_agf(mp, NULL, agno, 0, &agbp);
+       if (error)
+               return error;
+
+       cur = xfs_refcountbt_init_cursor(mp, NULL, agbp, agno, NULL);
+
+       error = xfs_refcount_find_shared(cur, agbno, aglen, fbno, flen,
+                       find_end_of_shared);
+
+       xfs_btree_del_cursor(cur, error ? XFS_BTREE_ERROR : XFS_BTREE_NOERROR);
+
+       xfs_buf_relse(agbp);
+       return error;
+}
+
+/*
+ * Trim the mapping to the next block where there's a change in the
+ * shared/unshared status.  More specifically, this means that we
+ * find the lowest-numbered extent of shared blocks that coincides with
+ * the given block mapping.  If the shared extent overlaps the start of
+ * the mapping, trim the mapping to the end of the shared extent.  If
+ * the shared region intersects the mapping, trim the mapping to the
+ * start of the shared extent.  If there are no shared regions that
+ * overlap, just return the original extent.
+ */
+int
+xfs_reflink_trim_around_shared(
+       struct xfs_inode        *ip,
+       struct xfs_bmbt_irec    *irec,
+       bool                    *shared,
+       bool                    *trimmed)
+{
+       xfs_agnumber_t          agno;
+       xfs_agblock_t           agbno;
+       xfs_extlen_t            aglen;
+       xfs_agblock_t           fbno;
+       xfs_extlen_t            flen;
+       int                     error = 0;
+
+       /* Holes, unwritten, and delalloc extents cannot be shared */
+       if (!xfs_is_reflink_inode(ip) ||
+           ISUNWRITTEN(irec) ||
+           irec->br_startblock == HOLESTARTBLOCK ||
+           irec->br_startblock == DELAYSTARTBLOCK) {
+               *shared = false;
+               return 0;
+       }
+
+       trace_xfs_reflink_trim_around_shared(ip, irec);
+
+       agno = XFS_FSB_TO_AGNO(ip->i_mount, irec->br_startblock);
+       agbno = XFS_FSB_TO_AGBNO(ip->i_mount, irec->br_startblock);
+       aglen = irec->br_blockcount;
+
+       error = xfs_reflink_find_shared(ip->i_mount, agno, agbno,
+                       aglen, &fbno, &flen, true);
+       if (error)
+               return error;
+
+       *shared = *trimmed = false;
+       if (fbno == NULLAGBLOCK) {
+               /* No shared blocks at all. */
+               return 0;
+       } else if (fbno == agbno) {
+               /*
+                * The start of this extent is shared.  Truncate the
+                * mapping at the end of the shared region so that a
+                * subsequent iteration starts at the start of the
+                * unshared region.
+                */
+               irec->br_blockcount = flen;
+               *shared = true;
+               if (flen != aglen)
+                       *trimmed = true;
+               return 0;
+       } else {
+               /*
+                * There's a shared extent midway through this extent.
+                * Truncate the mapping at the start of the shared
+                * extent so that a subsequent iteration starts at the
+                * start of the shared region.
+                */
+               irec->br_blockcount = fbno - agbno;
+               *trimmed = true;
+               return 0;
+       }
+}
+
+/* Create a CoW reservation for a range of blocks within a file. */
+static int
+__xfs_reflink_reserve_cow(
+       struct xfs_inode        *ip,
+       xfs_fileoff_t           *offset_fsb,
+       xfs_fileoff_t           end_fsb)
+{
+       struct xfs_bmbt_irec    got, prev, imap;
+       xfs_fileoff_t           orig_end_fsb;
+       int                     nimaps, eof = 0, error = 0;
+       bool                    shared = false, trimmed = false;
+       xfs_extnum_t            idx;
+
+       /* Already reserved?  Skip the refcount btree access. */
+       xfs_bmap_search_extents(ip, *offset_fsb, XFS_COW_FORK, &eof, &idx,
+                       &got, &prev);
+       if (!eof && got.br_startoff <= *offset_fsb) {
+               end_fsb = orig_end_fsb = got.br_startoff + got.br_blockcount;
+               trace_xfs_reflink_cow_found(ip, &got);
+               goto done;
+       }
+
+       /* Read extent from the source file. */
+       nimaps = 1;
+       error = xfs_bmapi_read(ip, *offset_fsb, end_fsb - *offset_fsb,
+                       &imap, &nimaps, 0);
+       if (error)
+               goto out_unlock;
+       ASSERT(nimaps == 1);
+
+       /* Trim the mapping to the nearest shared extent boundary. */
+       error = xfs_reflink_trim_around_shared(ip, &imap, &shared, &trimmed);
+       if (error)
+               goto out_unlock;
+
+       end_fsb = orig_end_fsb = imap.br_startoff + imap.br_blockcount;
+
+       /* Not shared?  Just report the (potentially capped) extent. */
+       if (!shared)
+               goto done;
+
+       /*
+        * Fork all the shared blocks from our write offset until the end of
+        * the extent.
+        */
+       error = xfs_qm_dqattach_locked(ip, 0);
+       if (error)
+               goto out_unlock;
+
+retry:
+       error = xfs_bmapi_reserve_delalloc(ip, XFS_COW_FORK, *offset_fsb,
+                       end_fsb - *offset_fsb, &got,
+                       &prev, &idx, eof);
+       switch (error) {
+       case 0:
+               break;
+       case -ENOSPC:
+       case -EDQUOT:
+               /* retry without any preallocation */
+               trace_xfs_reflink_cow_enospc(ip, &imap);
+               if (end_fsb != orig_end_fsb) {
+                       end_fsb = orig_end_fsb;
+                       goto retry;
+               }
+               /*FALLTHRU*/
+       default:
+               goto out_unlock;
+       }
+
+       trace_xfs_reflink_cow_alloc(ip, &got);
+done:
+       *offset_fsb = end_fsb;
+out_unlock:
+       return error;
+}
+
+/* Create a CoW reservation for part of a file. */
+int
+xfs_reflink_reserve_cow_range(
+       struct xfs_inode        *ip,
+       xfs_off_t               offset,
+       xfs_off_t               count)
+{
+       struct xfs_mount        *mp = ip->i_mount;
+       xfs_fileoff_t           offset_fsb, end_fsb;
+       int                     error;
+
+       trace_xfs_reflink_reserve_cow_range(ip, offset, count);
+
+       offset_fsb = XFS_B_TO_FSBT(mp, offset);
+       end_fsb = XFS_B_TO_FSB(mp, offset + count);
+
+       xfs_ilock(ip, XFS_ILOCK_EXCL);
+       while (offset_fsb < end_fsb) {
+               error = __xfs_reflink_reserve_cow(ip, &offset_fsb, end_fsb);
+               if (error) {
+                       trace_xfs_reflink_reserve_cow_range_error(ip, error,
+                               _RET_IP_);
+                       break;
+               }
+       }
+       xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
+       return error;
+}
index 820b151c82069a793bd1674727b1e40e82d57acf..f824f874234f8e8c55b94e924a57fbafd35c5b66 100644 (file)
 #ifndef __XFS_REFLINK_H
 #define __XFS_REFLINK_H 1
 
+extern int xfs_reflink_find_shared(struct xfs_mount *mp, xfs_agnumber_t agno,
+               xfs_agblock_t agbno, xfs_extlen_t aglen, xfs_agblock_t *fbno,
+               xfs_extlen_t *flen, bool find_maximal);
+extern int xfs_reflink_trim_around_shared(struct xfs_inode *ip,
+               struct xfs_bmbt_irec *irec, bool *shared, bool *trimmed);
+
+extern int xfs_reflink_reserve_cow_range(struct xfs_inode *ip,
+               xfs_off_t offset, xfs_off_t count);
+
 #endif /* __XFS_REFLINK_H */