summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2019-08-30 15:45:16 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2019-10-09 09:39:25 -0700
commit7725e22d826bd7d67406292fe1bf96a1098bd35a (patch)
treedf0ef8e7c354612afb79b626c08f34e16fe9c3a7
parent8c6a7be43c82a0d877f36c40a5fc7884522868f2 (diff)
xfs: implement live quotacheck as part of quota repair
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>
-rw-r--r--fs/xfs/scrub/quota.c22
-rw-r--r--fs/xfs/scrub/quota_repair.c139
-rw-r--r--fs/xfs/xfs_qm.c94
-rw-r--r--fs/xfs/xfs_qm.h3
4 files changed, 221 insertions, 37 deletions
diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c
index 9dd737aff144..bf825b7aad3c 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 @@ out:
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 ada1889acb9f..f5aff56f1a73 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -1130,11 +1130,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));
@@ -1146,16 +1147,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)) {
@@ -1206,6 +1210,8 @@ xfs_qm_dqusage_adjust(
}
error0:
+ if (ilock_flags)
+ xfs_iunlock(ip, ilock_flags);
xfs_irele(ip);
return error;
}
@@ -1263,16 +1269,60 @@ out_unlock:
}
/*
+ * 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;
@@ -1313,37 +1363,11 @@ 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
* dirty dquots that might be cached, we just want to get rid of them
diff --git a/fs/xfs/xfs_qm.h b/fs/xfs/xfs_qm.h
index b41b75089548..f2b3426993b7 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__ */