[RFC PATCH] xfs: support shrinking unused space in the last AG

Gao Xiang
2020-10-14T00:58:28Z

From: Gao Xiang <hsiangkao@redhat.com>

At the first step of shrinking, this attempts to enable shrinking
unused space in the last allocation group by fixing up freespace
btree, agi, agf and adjusting super block.

This can be all done in one transaction for now, so I think no
additional protection is needed.

Signed-off-by: Gao Xiang <hsiangkao@redhat.com>
---

Honestly, I've got headache about shrinking entire AGs
since the codebase doesn't expect agcount can be decreased
suddenly, I got some ideas from people but the modification
seems all over the codebase, I will keep on this at least
to learn more about XFS internals.

It might be worth sending out shrinking the last AG first
since users might need to shrink a little unused space
occasionally, yet I'm not quite sure the log space reservation
calculation in this patch and other details are correct.
I've done some manual test and it seems work. Yeah, as a
formal patch it needs more test to be done but I'd like
to hear more ideas about this first since I'm not quite
familiar with XFS for now and this topic involves a lot
new XFS-specific implementation details.

Kindly point out all strange places and what I'm missing
so I can revise it. It would be of great help for me to
learn more about XFS. At least just as a record on this
topic for further discussion.

Thanks,
Gao Xiang

fs/xfs/libxfs/xfs_ag.c | 46 ++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_ag.h | 2 ++
fs/xfs/libxfs/xfs_alloc.c | 55 +++++++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_alloc.h | 10 +++++++
fs/xfs/xfs_fsops.c | 49 ++++++++++++++++++++++++----------
fs/xfs/xfs_trans.c | 1 -
6 files changed, 148 insertions(+), 15 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_ag.c b/fs/xfs/libxfs/xfs_ag.c
index 9331f3516afa..cac7b715e90b 100644
--- a/fs/xfs/libxfs/xfs_ag.c
+++ b/fs/xfs/libxfs/xfs_ag.c
@@ -485,6 +485,52 @@ xfs_ag_init_headers(
return error;
}

+int
+xfs_ag_shrink_space(
+ struct xfs_mount *mp,
+ struct xfs_trans *tp,
+ struct aghdr_init_data *id,
+ xfs_extlen_t len)
+{
+ struct xfs_buf *agibp, *agfbp;
+ struct xfs_agi *agi;
+ struct xfs_agf *agf;
+ int error;
+
+ ASSERT(id->agno == mp->m_sb.sb_agcount - 1);
+ error = xfs_ialloc_read_agi(mp, tp, id->agno, &agibp);
+ if (error)
+ return error;
+
+ agi = agibp->b_addr;
+
+ /* Cannot touch the log space */
+ if (is_log_ag(mp, id) &&
+ XFS_FSB_TO_AGBNO(mp, mp->m_sb.sb_logstart) +
+ mp->m_sb.sb_logblocks > be32_to_cpu(agi->agi_length) - len)
+ return -EINVAL;
+
+ error = xfs_alloc_read_agf(mp, tp, id->agno, 0, &agfbp);
+ if (error)
+ return error;
+
+ error = xfs_alloc_vextent_shrink(tp, agfbp,
+ be32_to_cpu(agi->agi_length) - len, len);
+ if (error)
+ return error;
+
+ /* Change the agi length */
+ be32_add_cpu(&agi->agi_length, -len);
+ xfs_ialloc_log_agi(tp, agibp, XFS_AGI_LENGTH);
+
+ /* Change agf length */
+ agf = agfbp->b_addr;
+ be32_add_cpu(&agf->agf_length, -len);
+ ASSERT(agf->agf_length == agi->agi_length);
+ xfs_alloc_log_agf(tp, agfbp, XFS_AGF_LENGTH);
+ return 0;
+}
+
/*
* Extent the AG indicated by the @id by the length passed in
*/
diff --git a/fs/xfs/libxfs/xfs_ag.h b/fs/xfs/libxfs/xfs_ag.h
index 5166322807e7..f3b5bbfeadce 100644
--- a/fs/xfs/libxfs/xfs_ag.h
+++ b/fs/xfs/libxfs/xfs_ag.h
@@ -24,6 +24,8 @@ struct aghdr_init_data {
};

int xfs_ag_init_headers(struct xfs_mount *mp, struct aghdr_init_data *id);
+int xfs_ag_shrink_space(struct xfs_mount *mp, struct xfs_trans *tp,
+ struct aghdr_init_data *id, xfs_extlen_t len);
int xfs_ag_extend_space(struct xfs_mount *mp, struct xfs_trans *tp,
struct aghdr_init_data *id, xfs_extlen_t len);
int xfs_ag_get_geometry(struct xfs_mount *mp, xfs_agnumber_t agno,
diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c
index 852b536551b5..681357bb2701 100644
--- a/fs/xfs/libxfs/xfs_alloc.c
+++ b/fs/xfs/libxfs/xfs_alloc.c
@@ -1118,6 +1118,61 @@ xfs_alloc_ag_vextent_small(
return error;
}

+/*
+ * Allocate an extent for shrinking the last allocation group
+ * to fix the freespace btree.
+ * agfl fix is avoided in order to keep from dirtying the
+ * transaction unnecessarily compared with xfs_alloc_vextent().
+ */
+int
+xfs_alloc_vextent_shrink(
+ struct xfs_trans *tp,
+ struct xfs_buf *agbp,
+ xfs_agblock_t agbno,
+ xfs_extlen_t len)
+{
+ struct xfs_mount *mp = tp->t_mountp;
+ xfs_agnumber_t agno = agbp->b_pag->pag_agno;
+ struct xfs_alloc_arg args = {
+ .tp = tp,
+ .mp = mp,
+ .type = XFS_ALLOCTYPE_THIS_BNO,
+ .agbp = agbp,
+ .agno = agno,
+ .agbno = agbno,
+ .fsbno = XFS_AGB_TO_FSB(mp, agno, agbno),
+ .minlen = len,
+ .maxlen = len,
+ .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
+ .resv = XFS_AG_RESV_NONE,
+ .prod = 1,
+ .alignment = 1,
+ .pag = agbp->b_pag
+ };
+ int error;
+
+ error = xfs_alloc_ag_vextent_exact(&args);
+ if (error || args.agbno == NULLAGBLOCK)
+ return -EBUSY;
+
+ ASSERT(args.agbno == agbno);
+ ASSERT(args.len == len);
+ ASSERT(!args.wasfromfl || args.resv != XFS_AG_RESV_AGFL);
+
+ if (!args.wasfromfl) {
+ error = xfs_alloc_update_counters(tp, agbp, -(long)len);
+ if (error)
+ return error;
+
+ ASSERT(!xfs_extent_busy_search(mp, args.agno, agbno, args.len));
+ }
+ xfs_ag_resv_alloc_extent(args.pag, args.resv, &args);
+
+ XFS_STATS_INC(mp, xs_allocx);
+ XFS_STATS_ADD(mp, xs_allocb, args.len);
+ return 0;
+}
+
/*
* Allocate a variable extent in the allocation group agno.
* Type and bno are used to determine where in the allocation group the
diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h
index 6c22b12176b8..6080140bcb56 100644
--- a/fs/xfs/libxfs/xfs_alloc.h
+++ b/fs/xfs/libxfs/xfs_alloc.h
@@ -160,6 +160,16 @@ int /* error */
xfs_alloc_vextent(
xfs_alloc_arg_t *args); /* allocation argument structure */

+/*
+ * Allocate an extent for shrinking the last AG
+ */
+int
+xfs_alloc_vextent_shrink(
+ struct xfs_trans *tp,
+ struct xfs_buf *agbp,
+ xfs_agblock_t agbno,
+ xfs_extlen_t len); /* length of extent */
+
/*
* Free an extent.
*/
diff --git a/fs/xfs/xfs_fsops.c b/fs/xfs/xfs_fsops.c
index ef1d5bb88b93..80927d323939 100644
--- a/fs/xfs/xfs_fsops.c
+++ b/fs/xfs/xfs_fsops.c
@@ -36,19 +36,21 @@ xfs_growfs_data_private(
xfs_rfsblock_t new;
xfs_agnumber_t oagcount;
xfs_trans_t *tp;
+ bool extend;
struct aghdr_init_data id = {};

nb = in->newblocks;
- if (nb < mp->m_sb.sb_dblocks)
- return -EINVAL;
if ((error = xfs_sb_validate_fsb_count(&mp->m_sb, nb)))
return error;
- error = xfs_buf_read_uncached(mp->m_ddev_targp,
+
+ if (nb > mp->m_sb.sb_dblocks) {
+ error = xfs_buf_read_uncached(mp->m_ddev_targp,
XFS_FSB_TO_BB(mp, nb) - XFS_FSS_TO_BB(mp, 1),
XFS_FSS_TO_BB(mp, 1), 0, &bp, NULL);
- if (error)
- return error;
- xfs_buf_relse(bp);
+ if (error)
+ return error;
+ xfs_buf_relse(bp);
+ }

new = nb; /* use new as a temporary here */
nb_mod = do_div(new, mp->m_sb.sb_agblocks);
@@ -56,10 +58,18 @@ xfs_growfs_data_private(
if (nb_mod && nb_mod < XFS_MIN_AG_BLOCKS) {
nagcount--;
nb = (xfs_rfsblock_t)nagcount * mp->m_sb.sb_agblocks;
- if (nb < mp->m_sb.sb_dblocks)
+ if (!nagcount)
return -EINVAL;
}
- new = nb - mp->m_sb.sb_dblocks;
+
+ if (nb > mp->m_sb.sb_dblocks) {
+ new = nb - mp->m_sb.sb_dblocks;
+ extend = true;
+ } else {
+ new = mp->m_sb.sb_dblocks - nb;
+ extend = false;
+ }
+
oagcount = mp->m_sb.sb_agcount;

/* allocate the new per-ag structures */
@@ -67,10 +77,14 @@ xfs_growfs_data_private(
error = xfs_initialize_perag(mp, nagcount, &nagimax);
if (error)
return error;
+ } else if (nagcount != oagcount) {
+ /* TODO: shrinking a whole AG hasn't yet implemented */
+ return -EINVAL;
}

error = xfs_trans_alloc(mp, &M_RES(mp)->tr_growdata,
- XFS_GROWFS_SPACE_RES(mp), 0, XFS_TRANS_RESERVE, &tp);
+ (extend ? 0 : new) + XFS_GROWFS_SPACE_RES(mp), 0,
+ XFS_TRANS_RESERVE, &tp);
if (error)
return error;

@@ -103,15 +117,22 @@ xfs_growfs_data_private(
goto out_trans_cancel;
}
}
- error = xfs_buf_delwri_submit(&id.buffer_list);
- if (error)
- goto out_trans_cancel;
+
+ if (!list_empty(&id.buffer_list)) {
+ error = xfs_buf_delwri_submit(&id.buffer_list);
+ if (error)
+ goto out_trans_cancel;
+ }

xfs_trans_agblocks_delta(tp, id.nfree);

/* If there are new blocks in the old last AG, extend it. */
if (new) {
- error = xfs_ag_extend_space(mp, tp, &id, new);
+ if (extend)
+ error = xfs_ag_extend_space(mp, tp, &id, new);
+ else
+ error = xfs_ag_shrink_space(mp, tp, &id, new);
+
if (error)
goto out_trans_cancel;
}
@@ -123,7 +144,7 @@ xfs_growfs_data_private(
*/
if (nagcount > oagcount)
xfs_trans_mod_sb(tp, XFS_TRANS_SB_AGCOUNT, nagcount - oagcount);
- if (nb > mp->m_sb.sb_dblocks)
+ if (nb != mp->m_sb.sb_dblocks)
xfs_trans_mod_sb(tp, XFS_TRANS_SB_DBLOCKS,
nb - mp->m_sb.sb_dblocks);
if (id.nfree)
diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c
index c94e71f741b6..81b9c32f9bef 100644
--- a/fs/xfs/xfs_trans.c
+++ b/fs/xfs/xfs_trans.c
@@ -419,7 +419,6 @@ xfs_trans_mod_sb(
tp->t_res_frextents_delta += delta;
break;
case XFS_TRANS_SB_DBLOCKS:
- ASSERT(delta > 0);
tp->t_dblocks_delta += delta;
break;
case XFS_TRANS_SB_AGCOUNT:
--
2.24.0