| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * Copyright (C) 2018-2023 Oracle. All Rights Reserved.
- * Author: Darrick J. Wong <djwong@kernel.org>
- */
- #include "xfs.h"
- #include "xfs_fs.h"
- #include "xfs_shared.h"
- #include "xfs_format.h"
- #include "xfs_trans_resv.h"
- #include "xfs_mount.h"
- #include "xfs_defer.h"
- #include "xfs_btree.h"
- #include "xfs_bit.h"
- #include "xfs_log_format.h"
- #include "xfs_trans.h"
- #include "xfs_sb.h"
- #include "xfs_inode.h"
- #include "xfs_inode_fork.h"
- #include "xfs_alloc.h"
- #include "xfs_bmap.h"
- #include "xfs_quota.h"
- #include "xfs_qm.h"
- #include "xfs_dquot.h"
- #include "xfs_dquot_item.h"
- #include "xfs_reflink.h"
- #include "xfs_bmap_btree.h"
- #include "xfs_trans_space.h"
- #include "scrub/xfs_scrub.h"
- #include "scrub/scrub.h"
- #include "scrub/common.h"
- #include "scrub/quota.h"
- #include "scrub/trace.h"
- #include "scrub/repair.h"
- /*
- * Quota Repair
- * ============
- *
- * Quota repairs are fairly simplistic; we fix everything that the dquot
- * 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.
- */
- struct xrep_quota_info {
- struct xfs_scrub *sc;
- bool need_quotacheck;
- };
- /*
- * Allocate a new block into a sparse hole in the quota file backing this
- * dquot, initialize the block, and commit the whole mess.
- */
- STATIC int
- xrep_quota_item_fill_bmap_hole(
- struct xfs_scrub *sc,
- struct xfs_dquot *dq,
- struct xfs_bmbt_irec *irec)
- {
- struct xfs_buf *bp;
- struct xfs_mount *mp = sc->mp;
- int nmaps = 1;
- int error;
- xfs_trans_ijoin(sc->tp, sc->ip, 0);
- /* Map a block into the file. */
- error = xfs_trans_reserve_more(sc->tp, XFS_QM_DQALLOC_SPACE_RES(mp),
- 0);
- if (error)
- return error;
- error = xfs_bmapi_write(sc->tp, sc->ip, dq->q_fileoffset,
- XFS_DQUOT_CLUSTER_SIZE_FSB, XFS_BMAPI_METADATA, 0,
- irec, &nmaps);
- if (error)
- return error;
- dq->q_blkno = XFS_FSB_TO_DADDR(mp, irec->br_startblock);
- trace_xrep_dquot_item_fill_bmap_hole(sc->mp, dq->q_type, dq->q_id);
- /* Initialize the new block. */
- error = xfs_trans_get_buf(sc->tp, mp->m_ddev_targp, dq->q_blkno,
- mp->m_quotainfo->qi_dqchunklen, 0, &bp);
- if (error)
- return error;
- bp->b_ops = &xfs_dquot_buf_ops;
- xfs_qm_init_dquot_blk(sc->tp, dq->q_id, dq->q_type, bp);
- xfs_buf_set_ref(bp, XFS_DQUOT_REF);
- /*
- * Finish the mapping transactions and roll one more time to
- * disconnect sc->ip from sc->tp.
- */
- error = xrep_defer_finish(sc);
- if (error)
- return error;
- return xfs_trans_roll(&sc->tp);
- }
- /* Make sure there's a written block backing this dquot */
- STATIC int
- xrep_quota_item_bmap(
- struct xfs_scrub *sc,
- struct xfs_dquot *dq,
- bool *dirty)
- {
- struct xfs_bmbt_irec irec;
- struct xfs_mount *mp = sc->mp;
- struct xfs_quotainfo *qi = mp->m_quotainfo;
- xfs_fileoff_t offset = dq->q_id / qi->qi_dqperchunk;
- int nmaps = 1;
- int error;
- /* The computed file offset should always be valid. */
- if (!xfs_verify_fileoff(mp, offset)) {
- ASSERT(xfs_verify_fileoff(mp, offset));
- return -EFSCORRUPTED;
- }
- dq->q_fileoffset = offset;
- error = xfs_bmapi_read(sc->ip, offset, 1, &irec, &nmaps, 0);
- if (error)
- return error;
- if (nmaps < 1 || !xfs_bmap_is_real_extent(&irec)) {
- /* Hole/delalloc extent; allocate a real block. */
- error = xrep_quota_item_fill_bmap_hole(sc, dq, &irec);
- if (error)
- return error;
- } else if (irec.br_state != XFS_EXT_NORM) {
- /* Unwritten extent, which we already took care of? */
- ASSERT(irec.br_state == XFS_EXT_NORM);
- return -EFSCORRUPTED;
- } else if (dq->q_blkno != XFS_FSB_TO_DADDR(mp, irec.br_startblock)) {
- /*
- * If the cached daddr is incorrect, repair probably punched a
- * hole out of the quota file and filled it back in with a new
- * block. Update the block mapping in the dquot.
- */
- dq->q_blkno = XFS_FSB_TO_DADDR(mp, irec.br_startblock);
- }
- *dirty = true;
- return 0;
- }
- /* Reset quota timers if incorrectly set. */
- static inline void
- xrep_quota_item_timer(
- struct xfs_scrub *sc,
- const struct xfs_dquot_res *res,
- bool *dirty)
- {
- if ((res->softlimit && res->count > res->softlimit) ||
- (res->hardlimit && res->count > res->hardlimit)) {
- if (!res->timer)
- *dirty = true;
- } else {
- if (res->timer)
- *dirty = true;
- }
- }
- /* Scrub the fields in an individual quota item. */
- STATIC int
- xrep_quota_item(
- struct xrep_quota_info *rqi,
- struct xfs_dquot *dq)
- {
- struct xfs_scrub *sc = rqi->sc;
- struct xfs_mount *mp = sc->mp;
- xfs_ino_t fs_icount;
- bool dirty = false;
- int error = 0;
- /* Last chance to abort before we start committing fixes. */
- if (xchk_should_terminate(sc, &error))
- return error;
- /*
- * We might need to fix holes in the bmap record for the storage
- * backing this dquot, so we need to lock the dquot and the quota file.
- * dqiterate gave us a locked dquot, so drop the dquot lock to get the
- * ILOCK_EXCL.
- */
- xfs_dqunlock(dq);
- xchk_ilock(sc, XFS_ILOCK_EXCL);
- xfs_dqlock(dq);
- error = xrep_quota_item_bmap(sc, dq, &dirty);
- xchk_iunlock(sc, XFS_ILOCK_EXCL);
- if (error)
- return error;
- /* Check the limits. */
- if (dq->q_blk.softlimit > dq->q_blk.hardlimit) {
- dq->q_blk.softlimit = dq->q_blk.hardlimit;
- dirty = true;
- }
- if (dq->q_ino.softlimit > dq->q_ino.hardlimit) {
- dq->q_ino.softlimit = dq->q_ino.hardlimit;
- dirty = true;
- }
- if (dq->q_rtb.softlimit > dq->q_rtb.hardlimit) {
- dq->q_rtb.softlimit = dq->q_rtb.hardlimit;
- dirty = true;
- }
- /*
- * Check that usage doesn't exceed physical limits. However, on
- * a reflink filesystem we're allowed to exceed physical space
- * if there are no quota limits. We don't know what the real number
- * is, but we can make quotacheck find out for us.
- */
- if (!xfs_has_reflink(mp) && dq->q_blk.count > mp->m_sb.sb_dblocks) {
- dq->q_blk.reserved -= dq->q_blk.count;
- dq->q_blk.reserved += mp->m_sb.sb_dblocks;
- dq->q_blk.count = mp->m_sb.sb_dblocks;
- rqi->need_quotacheck = true;
- dirty = true;
- }
- fs_icount = percpu_counter_sum(&mp->m_icount);
- if (dq->q_ino.count > fs_icount) {
- dq->q_ino.reserved -= dq->q_ino.count;
- dq->q_ino.reserved += fs_icount;
- dq->q_ino.count = fs_icount;
- rqi->need_quotacheck = true;
- dirty = true;
- }
- if (dq->q_rtb.count > mp->m_sb.sb_rblocks) {
- dq->q_rtb.reserved -= dq->q_rtb.count;
- dq->q_rtb.reserved += mp->m_sb.sb_rblocks;
- dq->q_rtb.count = mp->m_sb.sb_rblocks;
- rqi->need_quotacheck = true;
- dirty = true;
- }
- xrep_quota_item_timer(sc, &dq->q_blk, &dirty);
- xrep_quota_item_timer(sc, &dq->q_ino, &dirty);
- xrep_quota_item_timer(sc, &dq->q_rtb, &dirty);
- if (!dirty)
- return 0;
- trace_xrep_dquot_item(sc->mp, dq->q_type, dq->q_id);
- dq->q_flags |= XFS_DQFLAG_DIRTY;
- xfs_trans_dqjoin(sc->tp, dq);
- if (dq->q_id) {
- xfs_qm_adjust_dqlimits(dq);
- xfs_qm_adjust_dqtimers(dq);
- }
- xfs_trans_log_dquot(sc->tp, dq);
- error = xfs_trans_roll(&sc->tp);
- xfs_dqlock(dq);
- return error;
- }
- /* Fix a quota timer so that we can pass the verifier. */
- STATIC void
- xrep_quota_fix_timer(
- struct xfs_mount *mp,
- const struct xfs_disk_dquot *ddq,
- __be64 softlimit,
- __be64 countnow,
- __be32 *timer,
- time64_t timelimit)
- {
- uint64_t soft = be64_to_cpu(softlimit);
- uint64_t count = be64_to_cpu(countnow);
- time64_t new_timer;
- uint32_t t;
- if (!soft || count <= soft || *timer != 0)
- return;
- new_timer = xfs_dquot_set_timeout(mp,
- ktime_get_real_seconds() + timelimit);
- if (ddq->d_type & XFS_DQTYPE_BIGTIME)
- t = xfs_dq_unix_to_bigtime(new_timer);
- else
- t = new_timer;
- *timer = cpu_to_be32(t);
- }
- /* Fix anything the verifiers complain about. */
- STATIC int
- xrep_quota_block(
- struct xfs_scrub *sc,
- xfs_daddr_t daddr,
- xfs_dqtype_t dqtype,
- xfs_dqid_t id)
- {
- struct xfs_dqblk *dqblk;
- struct xfs_disk_dquot *ddq;
- struct xfs_quotainfo *qi = sc->mp->m_quotainfo;
- struct xfs_def_quota *defq = xfs_get_defquota(qi, dqtype);
- struct xfs_buf *bp = NULL;
- enum xfs_blft buftype = 0;
- int i;
- int error;
- error = xfs_trans_read_buf(sc->mp, sc->tp, sc->mp->m_ddev_targp, daddr,
- qi->qi_dqchunklen, 0, &bp, &xfs_dquot_buf_ops);
- switch (error) {
- case -EFSBADCRC:
- case -EFSCORRUPTED:
- /* Failed verifier, retry read with no ops. */
- error = xfs_trans_read_buf(sc->mp, sc->tp,
- sc->mp->m_ddev_targp, daddr, qi->qi_dqchunklen,
- 0, &bp, NULL);
- if (error)
- return error;
- break;
- case 0:
- dqblk = bp->b_addr;
- ddq = &dqblk[0].dd_diskdq;
- /*
- * If there's nothing that would impede a dqiterate, we're
- * done.
- */
- if ((ddq->d_type & XFS_DQTYPE_REC_MASK) != dqtype ||
- id == be32_to_cpu(ddq->d_id)) {
- xfs_trans_brelse(sc->tp, bp);
- return 0;
- }
- break;
- default:
- return error;
- }
- /* Something's wrong with the block, fix the whole thing. */
- dqblk = bp->b_addr;
- bp->b_ops = &xfs_dquot_buf_ops;
- for (i = 0; i < qi->qi_dqperchunk; i++, dqblk++) {
- ddq = &dqblk->dd_diskdq;
- trace_xrep_disk_dquot(sc->mp, dqtype, id + i);
- ddq->d_magic = cpu_to_be16(XFS_DQUOT_MAGIC);
- ddq->d_version = XFS_DQUOT_VERSION;
- ddq->d_type = dqtype;
- ddq->d_id = cpu_to_be32(id + i);
- if (xfs_has_bigtime(sc->mp) && ddq->d_id)
- ddq->d_type |= XFS_DQTYPE_BIGTIME;
- xrep_quota_fix_timer(sc->mp, ddq, ddq->d_blk_softlimit,
- ddq->d_bcount, &ddq->d_btimer,
- defq->blk.time);
- xrep_quota_fix_timer(sc->mp, ddq, ddq->d_ino_softlimit,
- ddq->d_icount, &ddq->d_itimer,
- defq->ino.time);
- xrep_quota_fix_timer(sc->mp, ddq, ddq->d_rtb_softlimit,
- ddq->d_rtbcount, &ddq->d_rtbtimer,
- defq->rtb.time);
- /* We only support v5 filesystems so always set these. */
- uuid_copy(&dqblk->dd_uuid, &sc->mp->m_sb.sb_meta_uuid);
- xfs_update_cksum((char *)dqblk, sizeof(struct xfs_dqblk),
- XFS_DQUOT_CRC_OFF);
- dqblk->dd_lsn = 0;
- }
- switch (dqtype) {
- case XFS_DQTYPE_USER:
- buftype = XFS_BLFT_UDQUOT_BUF;
- break;
- case XFS_DQTYPE_GROUP:
- buftype = XFS_BLFT_GDQUOT_BUF;
- break;
- case XFS_DQTYPE_PROJ:
- buftype = XFS_BLFT_PDQUOT_BUF;
- break;
- }
- xfs_trans_buf_set_type(sc->tp, bp, buftype);
- xfs_trans_log_buf(sc->tp, bp, 0, BBTOB(bp->b_length) - 1);
- return xrep_roll_trans(sc);
- }
- /*
- * Repair a quota file's data fork. The function returns with the inode
- * joined.
- */
- STATIC int
- xrep_quota_data_fork(
- struct xfs_scrub *sc,
- xfs_dqtype_t dqtype)
- {
- struct xfs_bmbt_irec irec = { 0 };
- struct xfs_iext_cursor icur;
- struct xfs_quotainfo *qi = sc->mp->m_quotainfo;
- struct xfs_ifork *ifp;
- xfs_fileoff_t max_dqid_off;
- xfs_fileoff_t off;
- xfs_fsblock_t fsbno;
- bool truncate = false;
- bool joined = false;
- int error = 0;
- error = xrep_metadata_inode_forks(sc);
- if (error)
- goto out;
- /* Check for data fork problems that apply only to quota files. */
- max_dqid_off = XFS_DQ_ID_MAX / qi->qi_dqperchunk;
- ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK);
- for_each_xfs_iext(ifp, &icur, &irec) {
- if (isnullstartblock(irec.br_startblock)) {
- error = -EFSCORRUPTED;
- goto out;
- }
- if (irec.br_startoff > max_dqid_off ||
- irec.br_startoff + irec.br_blockcount - 1 > max_dqid_off) {
- truncate = true;
- break;
- }
- /* Convert unwritten extents to real ones. */
- if (irec.br_state == XFS_EXT_UNWRITTEN) {
- struct xfs_bmbt_irec nrec;
- int nmap = 1;
- if (!joined) {
- xfs_trans_ijoin(sc->tp, sc->ip, 0);
- joined = true;
- }
- error = xfs_bmapi_write(sc->tp, sc->ip,
- irec.br_startoff, irec.br_blockcount,
- XFS_BMAPI_CONVERT, 0, &nrec, &nmap);
- if (error)
- goto out;
- ASSERT(nrec.br_startoff == irec.br_startoff);
- ASSERT(nrec.br_blockcount == irec.br_blockcount);
- error = xfs_defer_finish(&sc->tp);
- if (error)
- goto out;
- }
- }
- if (!joined) {
- xfs_trans_ijoin(sc->tp, sc->ip, 0);
- joined = true;
- }
- if (truncate) {
- /* Erase everything after the block containing the max dquot */
- error = xfs_bunmapi_range(&sc->tp, sc->ip, 0,
- max_dqid_off * sc->mp->m_sb.sb_blocksize,
- XFS_MAX_FILEOFF);
- if (error)
- goto out;
- /* Remove all CoW reservations. */
- error = xfs_reflink_cancel_cow_blocks(sc->ip, &sc->tp, 0,
- XFS_MAX_FILEOFF, true);
- if (error)
- goto out;
- sc->ip->i_diflags2 &= ~XFS_DIFLAG2_REFLINK;
- /*
- * Always re-log the inode so that our permanent transaction
- * can keep on rolling it forward in the log.
- */
- xfs_trans_log_inode(sc->tp, sc->ip, XFS_ILOG_CORE);
- }
- /* Now go fix anything that fails the verifiers. */
- for_each_xfs_iext(ifp, &icur, &irec) {
- for (fsbno = irec.br_startblock, off = irec.br_startoff;
- fsbno < irec.br_startblock + irec.br_blockcount;
- fsbno += XFS_DQUOT_CLUSTER_SIZE_FSB,
- off += XFS_DQUOT_CLUSTER_SIZE_FSB) {
- error = xrep_quota_block(sc,
- XFS_FSB_TO_DADDR(sc->mp, fsbno),
- dqtype, off * qi->qi_dqperchunk);
- if (error)
- goto out;
- }
- }
- 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
- * use the regular dquot functions.
- */
- STATIC int
- xrep_quota_problems(
- struct xfs_scrub *sc,
- xfs_dqtype_t dqtype)
- {
- struct xchk_dqiter cursor = { };
- struct xrep_quota_info rqi = { .sc = sc };
- struct xfs_dquot *dq;
- int error;
- xchk_dqiter_init(&cursor, sc, dqtype);
- while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) {
- error = xrep_quota_item(&rqi, dq);
- xfs_qm_dqput(dq);
- if (error)
- break;
- }
- if (error)
- return error;
- /* Make a quotacheck happen. */
- if (rqi.need_quotacheck)
- xrep_force_quotacheck(sc, dqtype);
- return 0;
- }
- /* Repair all of a quota type's items. */
- int
- xrep_quota(
- struct xfs_scrub *sc)
- {
- xfs_dqtype_t dqtype;
- int error;
- dqtype = xchk_quota_to_dqtype(sc);
- /*
- * Re-take the ILOCK so that we can fix any problems that we found
- * with the data fork mappings, or with the dquot bufs themselves.
- */
- if (!(sc->ilock_flags & XFS_ILOCK_EXCL))
- xchk_ilock(sc, XFS_ILOCK_EXCL);
- error = xrep_quota_data_fork(sc, dqtype);
- if (error)
- return error;
- /*
- * Finish deferred items and roll the transaction to unjoin the quota
- * inode from transaction so that we can unlock the quota inode; we
- * play only with dquots from now on.
- */
- error = xrep_defer_finish(sc);
- if (error)
- return error;
- error = xfs_trans_roll(&sc->tp);
- if (error)
- return error;
- xchk_iunlock(sc, sc->ilock_flags);
- /* Fix anything the dquot verifiers don't complain about. */
- error = xrep_quota_problems(sc, dqtype);
- if (error)
- return error;
- return xrep_trans_commit(sc);
- }
|