[4/5] xfs: implement live quotacheck as part of quota repair
diff mbox series

Message ID 157784103458.1364003.10041166419649712004.stgit@magnolia
State New
Headers show
Series
  • xfs: online repair of rmap/quota/summary counters
Related show

Commit Message

Darrick J. Wong Jan. 1, 2020, 1:10 a.m. UTC
From: Darrick J. Wong <darrick.wong@oracle.com>

Use the fs freezing mechanism we developed for the rmapbt repair to
freeze the fs, this time to scan the fs for a live quotacheck.  We add a
new dqget variant to use the existing scrub transaction to allocate an
on-disk dquot block if it is missing.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 fs/xfs/scrub/quota.c        |   22 ++++++-
 fs/xfs/scrub/quota_repair.c |  139 +++++++++++++++++++++++++++++++++++++++++++
 fs/xfs/xfs_qm.c             |   94 ++++++++++++++++++-----------
 fs/xfs/xfs_qm.h             |    3 +
 4 files changed, 221 insertions(+), 37 deletions(-)

Patch
diff mbox series

diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c
index bab55b6cd723..64e24fe5dcb2 100644
--- a/fs/xfs/scrub/quota.c
+++ b/fs/xfs/scrub/quota.c
@@ -16,6 +16,7 @@ 
 #include "xfs_qm.h"
 #include "scrub/scrub.h"
 #include "scrub/common.h"
+#include "scrub/repair.h"
 
 /* Convert a scrub type code to a DQ flag, or return 0 if error. */
 uint
@@ -53,12 +54,31 @@  xchk_setup_quota(
 	mutex_lock(&sc->mp->m_quotainfo->qi_quotaofflock);
 	if (!xfs_this_quota_on(sc->mp, dqtype))
 		return -ENOENT;
+
+	/*
+	 * Freeze out anything that can alter an inode because we reconstruct
+	 * the quota counts by iterating all the inodes in the system.
+	 */
+	if ((sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) &&
+	    ((sc->flags & XCHK_TRY_HARDER) || XFS_QM_NEED_QUOTACHECK(sc->mp))) {
+		error = xchk_fs_freeze(sc);
+		if (error)
+			return error;
+	}
+
 	error = xchk_setup_fs(sc, ip);
 	if (error)
 		return error;
 	sc->ip = xfs_quota_inode(sc->mp, dqtype);
-	xfs_ilock(sc->ip, XFS_ILOCK_EXCL);
 	sc->ilock_flags = XFS_ILOCK_EXCL;
+	/*
+	 * Pretend to be an ILOCK parent to shut up lockdep if we're going to
+	 * do a full inode scan of the fs.  Quota inodes do not count towards
+	 * quota accounting, so we shouldn't deadlock on ourselves.
+	 */
+	if (sc->flags & XCHK_FS_FROZEN)
+		sc->ilock_flags |= XFS_ILOCK_PARENT;
+	xfs_ilock(sc->ip, sc->ilock_flags);
 	return 0;
 }
 
diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c
index 5f76c4f4db1a..61d7e43ba56b 100644
--- a/fs/xfs/scrub/quota_repair.c
+++ b/fs/xfs/scrub/quota_repair.c
@@ -23,6 +23,11 @@ 
 #include "xfs_qm.h"
 #include "xfs_dquot.h"
 #include "xfs_dquot_item.h"
+#include "xfs_trans_space.h"
+#include "xfs_error.h"
+#include "xfs_errortag.h"
+#include "xfs_health.h"
+#include "xfs_iwalk.h"
 #include "scrub/xfs_scrub.h"
 #include "scrub/scrub.h"
 #include "scrub/common.h"
@@ -37,6 +42,11 @@ 
  * verifiers complain about, cap any counters or limits that make no sense,
  * and schedule a quotacheck if we had to fix anything.  We also repair any
  * data fork extent records that don't apply to metadata files.
+ *
+ * Online quotacheck is fairly straightforward.  We engage a repair freeze,
+ * zero all the dquots, and scan every inode in the system to recalculate the
+ * appropriate quota charges.  Finally, we log all the dquots to disk and
+ * set the _CHKD flags.
  */
 
 struct xrep_quota_info {
@@ -312,6 +322,116 @@  xrep_quota_data_fork(
 	return error;
 }
 
+/* Online Quotacheck */
+
+/*
+ * Zero a dquot prior to regenerating the counts.  We skip flushing the dirty
+ * dquots to disk because we've already cleared the CHKD flags in the ondisk
+ * superblock so if we crash we'll just rerun quotacheck.
+ */
+static int
+xrep_quotacheck_zero_dquot(
+	struct xfs_dquot	*dq,
+	uint			dqtype,
+	void			*priv)
+{
+	dq->q_res_bcount -= be64_to_cpu(dq->q_core.d_bcount);
+	dq->q_core.d_bcount = 0;
+	dq->q_res_icount -= be64_to_cpu(dq->q_core.d_icount);
+	dq->q_core.d_icount = 0;
+	dq->q_res_rtbcount -= be64_to_cpu(dq->q_core.d_rtbcount);
+	dq->q_core.d_rtbcount = 0;
+	dq->dq_flags |= XFS_DQ_DIRTY;
+	return 0;
+}
+
+/* Execute an online quotacheck. */
+STATIC int
+xrep_quotacheck(
+	struct xfs_scrub	*sc)
+{
+	LIST_HEAD		(buffer_list);
+	struct xfs_mount	*mp = sc->mp;
+	uint			qflag = 0;
+	int			error;
+
+	/*
+	 * We can rebuild all the quota information, so we need to be able to
+	 * update both the health status and the CHKD flags.
+	 */
+	if (XFS_IS_UQUOTA_ON(mp)) {
+		sc->sick_mask |= XFS_SICK_FS_UQUOTA;
+		qflag |= XFS_UQUOTA_CHKD;
+	}
+	if (XFS_IS_GQUOTA_ON(mp)) {
+		sc->sick_mask |= XFS_SICK_FS_GQUOTA;
+		qflag |= XFS_GQUOTA_CHKD;
+	}
+	if (XFS_IS_PQUOTA_ON(mp)) {
+		sc->sick_mask |= XFS_SICK_FS_PQUOTA;
+		qflag |= XFS_PQUOTA_CHKD;
+	}
+
+	/* Clear the CHKD flags. */
+	spin_lock(&sc->mp->m_sb_lock);
+	sc->mp->m_qflags &= ~qflag;
+	sc->mp->m_sb.sb_qflags &= ~qflag;
+	spin_unlock(&sc->mp->m_sb_lock);
+	xfs_log_sb(sc->tp);
+
+	/*
+	 * Commit the transaction so that we can allocate new quota ip
+	 * mappings if we have to.  If we crash after this point, the sb
+	 * still has the CHKD flags cleared, so mount quotacheck will fix
+	 * all of this up.
+	 */
+	error = xfs_trans_commit(sc->tp);
+	sc->tp = NULL;
+	if (error)
+		return error;
+
+	/*
+	 * Zero all the dquots, and remember that we rebuild all three quota
+	 * types.  We hold the quotaoff lock, so these won't change.
+	 */
+	if (XFS_IS_UQUOTA_ON(mp)) {
+		error = xfs_qm_dqiterate(mp, XFS_DQ_USER,
+				xrep_quotacheck_zero_dquot, NULL);
+		if (error)
+			goto out;
+	}
+	if (XFS_IS_GQUOTA_ON(mp)) {
+		error = xfs_qm_dqiterate(mp, XFS_DQ_GROUP,
+				xrep_quotacheck_zero_dquot, NULL);
+		if (error)
+			goto out;
+	}
+	if (XFS_IS_PQUOTA_ON(mp)) {
+		error = xfs_qm_dqiterate(mp, XFS_DQ_PROJ,
+				xrep_quotacheck_zero_dquot, NULL);
+		if (error)
+			goto out;
+	}
+
+	/* Walk the inodes and reset the dquots. */
+	error = xfs_qm_quotacheck_walk_and_flush(mp, true, &buffer_list);
+	if (error)
+		goto out;
+
+	/* Set quotachecked flag. */
+	error = xchk_trans_alloc(sc, 0);
+	if (error)
+		goto out;
+
+	spin_lock(&sc->mp->m_sb_lock);
+	sc->mp->m_qflags |= qflag;
+	sc->mp->m_sb.sb_qflags |= qflag;
+	spin_unlock(&sc->mp->m_sb_lock);
+	xfs_log_sb(sc->tp);
+out:
+	return error;
+}
+
 /*
  * Go fix anything in the quota items that we could have been mad about.  Now
  * that we've checked the quota inode data fork we have to drop ILOCK_EXCL to
@@ -332,8 +452,10 @@  xrep_quota_problems(
 		return error;
 
 	/* Make a quotacheck happen. */
-	if (rqi.need_quotacheck)
+	if (rqi.need_quotacheck ||
+	    XFS_TEST_ERROR(false, sc->mp, XFS_ERRTAG_FORCE_SCRUB_REPAIR))
 		xrep_force_quotacheck(sc, dqtype);
+
 	return 0;
 }
 
@@ -343,6 +465,7 @@  xrep_quota(
 	struct xfs_scrub	*sc)
 {
 	uint			dqtype;
+	uint			flag;
 	int			error;
 
 	dqtype = xchk_quota_to_dqtype(sc);
@@ -358,6 +481,20 @@  xrep_quota(
 
 	/* Fix anything the dquot verifiers complain about. */
 	error = xrep_quota_problems(sc, dqtype);
+	if (error)
+		goto out;
+
+	/* Do we need a quotacheck?  Did we need one? */
+	flag = xfs_quota_chkd_flag(dqtype);
+	if (!(flag & sc->mp->m_qflags)) {
+		/* We need to freeze the fs before we can scan inodes. */
+		if (!(sc->flags & XCHK_FS_FROZEN)) {
+			error = -EDEADLOCK;
+			goto out;
+		}
+
+		error = xrep_quotacheck(sc);
+	}
 out:
 	return error;
 }
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index fc3898f5e27d..0ce334c51d73 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -1140,11 +1140,12 @@  xfs_qm_dqusage_adjust(
 	struct xfs_mount	*mp,
 	struct xfs_trans	*tp,
 	xfs_ino_t		ino,
-	void			*data)
+	void			*need_ilocks)
 {
 	struct xfs_inode	*ip;
 	xfs_qcnt_t		nblks;
 	xfs_filblks_t		rtblks = 0;	/* total rt blks */
+	uint			ilock_flags = 0;
 	int			error;
 
 	ASSERT(XFS_IS_QUOTA_RUNNING(mp));
@@ -1156,16 +1157,19 @@  xfs_qm_dqusage_adjust(
 	if (xfs_is_quota_inode(&mp->m_sb, ino))
 		return 0;
 
-	/*
-	 * We don't _need_ to take the ilock EXCL here because quotacheck runs
-	 * at mount time and therefore nobody will be racing chown/chproj.
-	 */
+	/* Grab inode and lock it if needed. */
 	error = xfs_iget(mp, tp, ino, XFS_IGET_DONTCACHE, 0, &ip);
 	if (error == -EINVAL || error == -ENOENT)
 		return 0;
 	if (error)
 		return error;
 
+	if (need_ilocks) {
+		ilock_flags = XFS_IOLOCK_SHARED | XFS_MMAPLOCK_SHARED;
+		xfs_ilock(ip, ilock_flags);
+		ilock_flags |= xfs_ilock_data_map_shared(ip);
+	}
+
 	ASSERT(ip->i_delayed_blks == 0);
 
 	if (XFS_IS_REALTIME_INODE(ip)) {
@@ -1216,6 +1220,8 @@  xfs_qm_dqusage_adjust(
 	}
 
 error0:
+	if (ilock_flags)
+		xfs_iunlock(ip, ilock_flags);
 	xfs_irele(ip);
 	return error;
 }
@@ -1272,17 +1278,61 @@  xfs_qm_flush_one(
 	return error;
 }
 
+/*
+ * Walk the inodes and adjust quota usage.  Caller must have previously
+ * zeroed all dquots.
+ */
+int
+xfs_qm_quotacheck_walk_and_flush(
+	struct xfs_mount	*mp,
+	bool			need_ilocks,
+	struct list_head	*buffer_list)
+{
+	int			error, error2;
+
+	error = xfs_iwalk_threaded(mp, 0, 0, xfs_qm_dqusage_adjust, 0,
+			!need_ilocks, NULL);
+	if (error)
+		return error;
+
+	/*
+	 * We've made all the changes that we need to make incore.  Flush them
+	 * down to disk buffers if everything was updated successfully.
+	 */
+	if (XFS_IS_UQUOTA_ON(mp)) {
+		error = xfs_qm_dquot_walk(mp, XFS_DQ_USER, xfs_qm_flush_one,
+					  buffer_list);
+	}
+	if (XFS_IS_GQUOTA_ON(mp)) {
+		error2 = xfs_qm_dquot_walk(mp, XFS_DQ_GROUP, xfs_qm_flush_one,
+					   buffer_list);
+		if (!error)
+			error = error2;
+	}
+	if (XFS_IS_PQUOTA_ON(mp)) {
+		error2 = xfs_qm_dquot_walk(mp, XFS_DQ_PROJ, xfs_qm_flush_one,
+					   buffer_list);
+		if (!error)
+			error = error2;
+	}
+
+	error2 = xfs_buf_delwri_submit(buffer_list);
+	if (!error)
+		error = error2;
+	return error;
+}
+
 /*
  * Walk thru all the filesystem inodes and construct a consistent view
  * of the disk quota world. If the quotacheck fails, disable quotas.
  */
 STATIC int
 xfs_qm_quotacheck(
-	xfs_mount_t	*mp)
+	struct xfs_mount	*mp)
 {
-	int			error, error2;
-	uint			flags;
+	int			error;
 	LIST_HEAD		(buffer_list);
+	uint			flags;
 	struct xfs_inode	*uip = mp->m_quotainfo->qi_uquotaip;
 	struct xfs_inode	*gip = mp->m_quotainfo->qi_gquotaip;
 	struct xfs_inode	*pip = mp->m_quotainfo->qi_pquotaip;
@@ -1323,36 +1373,10 @@  xfs_qm_quotacheck(
 		flags |= XFS_PQUOTA_CHKD;
 	}
 
-	error = xfs_iwalk_threaded(mp, 0, 0, xfs_qm_dqusage_adjust, 0, true,
-			NULL);
+	error = xfs_qm_quotacheck_walk_and_flush(mp, false, &buffer_list);
 	if (error)
 		goto error_return;
 
-	/*
-	 * We've made all the changes that we need to make incore.  Flush them
-	 * down to disk buffers if everything was updated successfully.
-	 */
-	if (XFS_IS_UQUOTA_ON(mp)) {
-		error = xfs_qm_dquot_walk(mp, XFS_DQ_USER, xfs_qm_flush_one,
-					  &buffer_list);
-	}
-	if (XFS_IS_GQUOTA_ON(mp)) {
-		error2 = xfs_qm_dquot_walk(mp, XFS_DQ_GROUP, xfs_qm_flush_one,
-					   &buffer_list);
-		if (!error)
-			error = error2;
-	}
-	if (XFS_IS_PQUOTA_ON(mp)) {
-		error2 = xfs_qm_dquot_walk(mp, XFS_DQ_PROJ, xfs_qm_flush_one,
-					   &buffer_list);
-		if (!error)
-			error = error2;
-	}
-
-	error2 = xfs_buf_delwri_submit(&buffer_list);
-	if (!error)
-		error = error2;
-
 	/*
 	 * We can get this error if we couldn't do a dquot allocation inside
 	 * xfs_qm_dqusage_adjust (via bulkstat). We don't care about the
diff --git a/fs/xfs/xfs_qm.h b/fs/xfs/xfs_qm.h
index 7823af39008b..a3d9932f2e65 100644
--- a/fs/xfs/xfs_qm.h
+++ b/fs/xfs/xfs_qm.h
@@ -179,4 +179,7 @@  xfs_get_defquota(struct xfs_dquot *dqp, struct xfs_quotainfo *qi)
 	return defq;
 }
 
+int xfs_qm_quotacheck_walk_and_flush(struct xfs_mount *mp, bool need_ilocks,
+		struct list_head *buffer_list);
+
 #endif /* __XFS_QM_H__ */