xfs: don't allow reflink when the AG is low on space
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 3 Oct 2016 16:11:45 +0000 (09:11 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Wed, 5 Oct 2016 23:26:27 +0000 (16:26 -0700)
If the AG free space is down to the reserves, refuse to reflink our
way out of space.  Hopefully userspace will make a real copy and/or go
elsewhere.

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

index 8e894118295fec9154adc520abe814d931c7b74a..e92ccd3163916426ff7dc37694416c6a4794795f 100644 (file)
@@ -54,6 +54,8 @@
 #include "xfs_reflink.h"
 #include "xfs_iomap.h"
 #include "xfs_rmap_btree.h"
+#include "xfs_sb.h"
+#include "xfs_ag_resv.h"
 
 /*
  * Copy on Write of Shared Blocks
@@ -977,6 +979,31 @@ out_error:
        return error;
 }
 
+/*
+ * Do we have enough reserve in this AG to handle a reflink?  The refcount
+ * btree already reserved all the space it needs, but the rmap btree can grow
+ * infinitely, so we won't allow more reflinks when the AG is down to the
+ * btree reserves.
+ */
+static int
+xfs_reflink_ag_has_free_space(
+       struct xfs_mount        *mp,
+       xfs_agnumber_t          agno)
+{
+       struct xfs_perag        *pag;
+       int                     error = 0;
+
+       if (!xfs_sb_version_hasrmapbt(&mp->m_sb))
+               return 0;
+
+       pag = xfs_perag_get(mp, agno);
+       if (xfs_ag_resv_critical(pag, XFS_AG_RESV_AGFL) ||
+           xfs_ag_resv_critical(pag, XFS_AG_RESV_METADATA))
+               error = -ENOSPC;
+       xfs_perag_put(pag);
+       return error;
+}
+
 /*
  * Unmap a range of blocks from a file, then map other blocks into the hole.
  * The range to unmap is (destoff : destoff + srcioff + irec->br_blockcount).
@@ -1009,6 +1036,14 @@ xfs_reflink_remap_extent(
                        irec->br_startblock != DELAYSTARTBLOCK &&
                        !ISUNWRITTEN(irec));
 
+       /* No reflinking if we're low on space */
+       if (real_extent) {
+               error = xfs_reflink_ag_has_free_space(mp,
+                               XFS_FSB_TO_AGNO(mp, irec->br_startblock));
+               if (error)
+                       goto out;
+       }
+
        /* Start a rolling transaction to switch the mappings */
        resblks = XFS_EXTENTADD_SPACE_RES(ip->i_mount, XFS_DATA_FORK);
        error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0, 0, &tp);