summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_reflink.c
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2016-10-03 09:11:45 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2016-10-05 16:26:27 -0700
commit6fa164b865e44ac1d1ffc9a24ccd442f17acc4f6 (patch)
tree62b863d4de68d47af16edcfac1e2a5fa0b2f437f /fs/xfs/xfs_reflink.c
parent84d6961910ea7b3ae8d8338f5b4df25dea68cee9 (diff)
xfs: don't allow reflink when the AG is low on space
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>
Diffstat (limited to 'fs/xfs/xfs_reflink.c')
-rw-r--r--fs/xfs/xfs_reflink.c35
1 files changed, 35 insertions, 0 deletions
diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c
index 8e894118295f..e92ccd316391 100644
--- a/fs/xfs/xfs_reflink.c
+++ b/fs/xfs/xfs_reflink.c
@@ -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
@@ -978,6 +980,31 @@ out_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).
* The extent irec is mapped into dest at irec->br_startoff.
@@ -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);