From: Darrick J. Wong <darrick.wong@xxxxxxxxxx> 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@xxxxxxxxxx> --- 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(-) 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__ */