diff options
Diffstat (limited to 'fs/xfs/scrub')
-rw-r--r-- | fs/xfs/scrub/iscan.h | 7 | ||||
-rw-r--r-- | fs/xfs/scrub/quotacheck.c | 326 | ||||
-rw-r--r-- | fs/xfs/scrub/quotacheck.h | 10 |
3 files changed, 338 insertions, 5 deletions
diff --git a/fs/xfs/scrub/iscan.h b/fs/xfs/scrub/iscan.h index 8d3ef186fa94..9b06a6e23893 100644 --- a/fs/xfs/scrub/iscan.h +++ b/fs/xfs/scrub/iscan.h @@ -66,4 +66,11 @@ xchk_iscan_mark_locked(struct xchk_iscan *iscan, struct xfs_inode *ip) iscan->marked_ino = ip->i_ino; } +/* Decide if this inode was previously scanned. */ +static inline bool +xchk_iscan_marked(struct xchk_iscan *iscan, struct xfs_inode *ip) +{ + return iscan->marked_ino >= ip->i_ino; +} + #endif /* __XFS_SCRUB_ISCAN_H__ */ diff --git a/fs/xfs/scrub/quotacheck.c b/fs/xfs/scrub/quotacheck.c index 48e898230f15..89f4f833c3ad 100644 --- a/fs/xfs/scrub/quotacheck.c +++ b/fs/xfs/scrub/quotacheck.c @@ -34,16 +34,62 @@ * as the summation of the block usage counts for every file on the filesystem. * Therefore, we compute the correct icount, bcount, and rtbcount values by * creating a shadow quota counter structure and walking every inode. + * + * Because we are scanning a live filesystem, it's possible that another thread + * will try to update the quota counters for an inode that we've already + * scanned. This will cause our counts to be incorrect. Therefore, we hook + * the live transaction code in two places: (1) when the callers update the + * per-transaction dqtrx structure to log quota counter updates; and (2) when + * transaction commit actually logs those updates to the incore dquot. By + * shadowing transaction updates in this manner, live quotacheck can ensure + * by locking the dquot and the shadow structure that its own copies are not + * out of date. + * + * Note that we use srcu notifier hooks to minimize the overhead when live + * quotacheck is /not/ running. + */ + +/* Track the quota deltas for a dquot in a transaction. */ +struct xqcheck_dqtrx { + struct xfs_dquot *dqp; + int64_t icount_delta; + + int64_t bcount_delta; + int64_t delbcnt_delta; + + int64_t rtbcount_delta; + int64_t delrtb_delta; +}; + +#define XQCHECK_MAX_NR_DQTRXS (XFS_QM_TRANS_DQTYPES * XFS_QM_TRANS_MAXDQS) + +/* + * Track the quota deltas for all dquots attached to a transaction if the + * quota deltas are being applied to an inode that we already scanned. */ +struct xqcheck_dqacct { + struct rhash_head hash; + uintptr_t tp; + struct xqcheck_dqtrx dqtrx[XQCHECK_MAX_NR_DQTRXS]; + unsigned int refcount; +}; + +/* Free a shadow dquot accounting structure. */ +static void +xqcheck_dqacct_free( + void *ptr, + void *arg) +{ + struct xqcheck_dqacct *dqa = ptr; + + kmem_free(dqa); +} /* Set us up to scrub quota counters. */ int xchk_setup_quotacheck( struct xfs_scrub *sc) { - /* Not ready for general consumption yet. */ - return -EOPNOTSUPP; - if (!XFS_IS_QUOTA_ON(sc->mp)) return -ENOENT; @@ -80,7 +126,7 @@ xqcheck_get_shadow_dquot( return error; } -/* Update an incore dquot information. */ +/* Update an incore dquot information. Caller must hold the iscan lock. */ static int xqcheck_update_incore( struct xqcheck *xqc, @@ -117,6 +163,210 @@ xqcheck_update_incore( return error; } +/* Decide if this is the shadow dquot accounting structure for a transaction. */ +static int +xqcheck_dqacct_obj_cmpfn( + struct rhashtable_compare_arg *arg, + const void *obj) +{ + const uintptr_t *key = arg->key; + const struct xqcheck_dqacct *dqa = obj; + + if (dqa->tp != *key) + return 1; + return 0; +} + +static const struct rhashtable_params xqcheck_dqacct_hash_params = { + .min_size = 32, + .key_len = sizeof(uintptr_t), + .key_offset = offsetof(struct xqcheck_dqacct, tp), + .head_offset = offsetof(struct xqcheck_dqacct, hash), + .automatic_shrinking = true, + .obj_cmpfn = xqcheck_dqacct_obj_cmpfn, +}; + +/* Find a shadow dqtrx slot for the given dquot. */ +STATIC struct xqcheck_dqtrx * +xqcheck_get_dqtrx( + struct xqcheck_dqacct *dqa, + struct xfs_dquot *dqp) +{ + int i; + + for (i = 0; i < XQCHECK_MAX_NR_DQTRXS; i++) { + if (dqa->dqtrx[i].dqp == NULL || + dqa->dqtrx[i].dqp == dqp) + return &dqa->dqtrx[i]; + } + + return NULL; +} + +/* + * Create and fill out a quota delta tracking structure to shadow the updates + * going on in the regular quota code. + */ +static int +xqcheck_mod_dquot( + struct notifier_block *nb, + unsigned long arg, + void *data) +{ + struct xfs_trans_mod_dquot_params *p = data; + struct xqcheck *xqc; + struct xqcheck_dqacct *dqa; + struct xqcheck_dqtrx *dqtrx; + int error; + + xqc = container_of(nb, struct xqcheck, mod_hook); + + /* Skip quota reservation fields. */ + switch (p->field) { + case XFS_TRANS_DQ_BCOUNT: + case XFS_TRANS_DQ_DELBCOUNT: + case XFS_TRANS_DQ_ICOUNT: + case XFS_TRANS_DQ_RTBCOUNT: + case XFS_TRANS_DQ_DELRTBCOUNT: + break; + default: + return NOTIFY_DONE; + } + + /* Skip inodes that haven't been scanned yet. */ + xchk_iscan_lock(&xqc->iscan); + if (!xchk_iscan_marked(&xqc->iscan, p->ip) || xqc->hook_dead) + goto out_unlock; + + /* Make a shadow quota accounting tracker for this transaction. */ + dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tp, + xqcheck_dqacct_hash_params); + if (!dqa) { + dqa = kmem_zalloc(sizeof(*dqa), KM_MAYFAIL | KM_NOFS); + if (!dqa) + goto fail; + + dqa->tp = (uintptr_t)p->tp; + error = rhashtable_insert_fast(&xqc->shadow_dquot_acct, + &dqa->hash, xqcheck_dqacct_hash_params); + if (error) + goto fail; + } + + /* Find the shadow dqtrx (or an empty slot) here. */ + dqtrx = xqcheck_get_dqtrx(dqa, p->dqp); + if (!dqtrx) + goto fail; + if (dqtrx->dqp == NULL) { + dqtrx->dqp = p->dqp; + dqa->refcount++; + } + + /* Update counter */ + switch (p->field) { + case XFS_TRANS_DQ_BCOUNT: + dqtrx->bcount_delta += p->delta; + break; + case XFS_TRANS_DQ_DELBCOUNT: + dqtrx->delbcnt_delta += p->delta; + break; + case XFS_TRANS_DQ_ICOUNT: + dqtrx->icount_delta += p->delta; + break; + case XFS_TRANS_DQ_RTBCOUNT: + dqtrx->rtbcount_delta += p->delta; + break; + case XFS_TRANS_DQ_DELRTBCOUNT: + dqtrx->delrtb_delta += p->delta; + break; + } + + goto out_unlock; +fail: + xqc->hook_dead = true; +out_unlock: + xchk_iscan_unlock(&xqc->iscan); + return NOTIFY_DONE; +} + +/* + * Apply the transaction quota deltas to our shadow quota accounting info when + * the regular quota code are doing the same. + */ +static int +xqcheck_apply_deltas( + struct notifier_block *nb, + unsigned long arg, + void *data) +{ + struct xfs_trans_apply_dquot_deltas_params *p = data; + struct xqcheck *xqc; + struct xqcheck_dqacct *dqa; + struct xqcheck_dqtrx *dqtrx; + struct xfbma *counts; + int error; + + xqc = container_of(nb, struct xqcheck, apply_hook); + + /* Map the dquot type to an incore counter object. */ + switch (xfs_dquot_type(p->dqp)) { + case XFS_DQTYPE_USER: + counts = xqc->ucounts; + break; + case XFS_DQTYPE_GROUP: + counts = xqc->gcounts; + break; + case XFS_DQTYPE_PROJ: + counts = xqc->pcounts; + break; + default: + return NOTIFY_DONE; + } + + xchk_iscan_lock(&xqc->iscan); + if (xqc->hook_dead) + goto out_unlock; + + /* + * Find the shadow dqtrx for this transaction and dquot, if any deltas + * need to be applied here. + */ + dqa = rhashtable_lookup_fast(&xqc->shadow_dquot_acct, &p->tp, + xqcheck_dqacct_hash_params); + if (!dqa) + goto out_unlock; + dqtrx = xqcheck_get_dqtrx(dqa, p->dqp); + if (!dqtrx || dqtrx->dqp == NULL) + goto out_unlock; + + /* Update our shadow dquot. */ + if (arg) { + error = xqcheck_update_incore(xqc, counts, p->dqp->q_id, + dqtrx->icount_delta, + dqtrx->bcount_delta + dqtrx->delbcnt_delta, + dqtrx->rtbcount_delta + dqtrx->delrtb_delta); + if (error) + goto fail; + } + + /* Free the shadow accounting structure if that was the last user. */ + dqa->refcount--; + if (dqa->refcount == 0) { + error = rhashtable_remove_fast(&xqc->shadow_dquot_acct, + &dqa->hash, xqcheck_dqacct_hash_params); + if (error) + goto fail; + xqcheck_dqacct_free(dqa, NULL); + } + + goto out_unlock; +fail: + xqc->hook_dead = true; +out_unlock: + xchk_iscan_unlock(&xqc->iscan); + return NOTIFY_DONE; +} + /* Record this inode's quota usage in our shadow quota counter data. */ STATIC int xqcheck_inode( @@ -144,6 +394,11 @@ xqcheck_inode( xfs_inode_count_blocks(tp, ip, &nblks, &rtblks); xchk_iscan_lock(&xqc->iscan); + if (xqc->hook_dead) { + xchk_set_incomplete(xqc->sc); + error = -ECANCELED; + goto out_scan_lock; + } /* Update the shadow dquot counters. */ id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_USER); @@ -164,8 +419,10 @@ xqcheck_inode( xchk_iscan_mark_locked(&xqc->iscan, ip); out_scan_lock: - if (error) + if (error) { xchk_set_incomplete(xqc->sc); + xqc->hook_dead = true; + } xchk_iscan_unlock(&xqc->iscan); out_ilock: xfs_iunlock(ip, ilock_flags); @@ -245,6 +502,11 @@ xqcheck_compare_dquot( int error; xchk_iscan_lock(&xqc->iscan); + if (xqc->hook_dead) { + xchk_set_incomplete(xqc->sc); + error = -ECANCELED; + goto out_unlock; + } error = xqcheck_get_shadow_dquot(counts, dqp->q_id, &xcdq); if (error) @@ -352,6 +614,30 @@ static void xqcheck_teardown_scan( struct xqcheck *xqc) { + struct xfs_quotainfo *qi = xqc->sc->mp->m_quotainfo; + + /* Discourage any hook functions that might be running. */ + xchk_iscan_lock(&xqc->iscan); + xqc->hook_dead = true; + xchk_iscan_unlock(&xqc->iscan); + + /* + * As noted above, the apply hook is responsible for cleaning up the + * shadow dquot accounting data when a transaction completes. The mod + * hook must be removed before the apply hook so that we don't + * mistakenly leave an active shadow account for the mod hook to get + * its hands on. No hooks should be running after these functions + * return. + */ + xfs_hook_del(&qi->qi_mod_dquot_hooks, &xqc->mod_hook); + xfs_hook_del(&qi->qi_apply_dquot_deltas_hooks, &xqc->apply_hook); + + if (xqc->shadow_dquot_acct.key_len) { + rhashtable_free_and_destroy(&xqc->shadow_dquot_acct, + xqcheck_dqacct_free, NULL); + xqc->shadow_dquot_acct.key_len = 0; + } + if (xqc->pcounts) { xfbma_destroy(xqc->pcounts); xqc->pcounts = NULL; @@ -381,11 +667,13 @@ xqcheck_setup_scan( struct xfs_scrub *sc, struct xqcheck *xqc) { + struct xfs_quotainfo *qi = sc->mp->m_quotainfo; int error; ASSERT(xqc->sc == NULL); xqc->sc = sc; + xqc->hook_dead = false; xchk_iscan_start(&xqc->iscan); error = -ENOMEM; @@ -410,6 +698,34 @@ xqcheck_setup_scan( goto out_teardown; } + /* + * Set up hash table to map transactions to our internal shadow dqtrx + * structures. + */ + error = rhashtable_init(&xqc->shadow_dquot_acct, + &xqcheck_dqacct_hash_params); + if (error) + goto out_teardown; + + /* + * Hook into the quota code. The hook only triggers for inodes that + * were already scanned, and the scanner thread takes each inode's + * ILOCK, which means that any in-progress inode updates will finish + * before we can scan the inode. + * + * The apply hook (which removes the shadow dquot accounting struct) + * must be installed before the mod hook so that we never fail to catch + * the end of a quota update sequence and leave stale shadow data. + */ + error = xfs_hook_add(&qi->qi_apply_dquot_deltas_hooks, + &xqc->apply_hook, xqcheck_apply_deltas); + if (error) + goto out_teardown; + error = xfs_hook_add(&qi->qi_mod_dquot_hooks, &xqc->mod_hook, + xqcheck_mod_dquot); + if (error) + goto out_teardown; + /* Use deferred cleanup to pass the quota count data to repair. */ sc->buf_cleanup = (void (*)(void *))xqcheck_teardown_scan; return 0; diff --git a/fs/xfs/scrub/quotacheck.h b/fs/xfs/scrub/quotacheck.h index 69bc12fb81cf..660eac4e87cd 100644 --- a/fs/xfs/scrub/quotacheck.h +++ b/fs/xfs/scrub/quotacheck.h @@ -32,6 +32,16 @@ struct xqcheck { struct xfbma *pcounts; struct xchk_iscan iscan; + + /* Hooks into the quota code. */ + struct notifier_block mod_hook; + struct notifier_block apply_hook; + + /* Shadow quota delta tracking structure. */ + struct rhashtable shadow_dquot_acct; + + /* Something failed during live tracking. */ + bool hook_dead; }; /* Return the incore counter array for a given quota type. */ |