diff options
-rw-r--r-- | fs/xfs/scrub/quotacheck.c | 339 | ||||
-rw-r--r-- | fs/xfs/scrub/quotacheck.h | 7 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.c | 16 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.h | 15 | ||||
-rw-r--r-- | fs/xfs/xfs_quota.h | 22 | ||||
-rw-r--r-- | fs/xfs/xfs_trans_dquot.c | 72 |
6 files changed, 457 insertions, 14 deletions
diff --git a/fs/xfs/scrub/quotacheck.c b/fs/xfs/scrub/quotacheck.c index 53bb2f9ad9dd..a52a46e328be 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; @@ -88,6 +134,230 @@ xqcheck_update_incore_counts( 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_live_ino_dqtrx( + struct notifier_block *nb, + unsigned long field, + void *data) +{ + struct xfs_mod_ino_dqtrx_params *p = data; + struct xqcheck *xqc; + struct xqcheck_dqacct *dqa; + struct xqcheck_dqtrx *dqtrx; + int error; + + xqc = container_of(nb, struct xqcheck, mod_dqtrx_hook); + + /* Skip quota reservation fields. */ + switch (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; + } + + /* Ignore dqtrx updates for quota types we don't care about. */ + switch (xfs_dquot_type(p->dqp)) { + case XFS_DQTYPE_USER: + if (!xqc->ucounts) + return NOTIFY_DONE; + break; + case XFS_DQTYPE_GROUP: + if (!xqc->gcounts) + return NOTIFY_DONE; + break; + case XFS_DQTYPE_PROJ: + if (!xqc->pcounts) + return NOTIFY_DONE; + break; + default: + return NOTIFY_DONE; + } + + /* Skip inodes that haven't been scanned yet. */ + if (!xchk_iscan_want_live_update(&xqc->iscan, p->ip->i_ino)) + goto out_done; + + /* Make a shadow quota accounting tracker for this transaction. */ + mutex_lock(&xqc->lock); + 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 (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; + } + +out_unlock: + mutex_unlock(&xqc->lock); +out_done: + return NOTIFY_DONE; +fail: + xchk_iscan_abort(&xqc->iscan); + goto out_unlock; +} + +/* + * Apply the transaction quota deltas to our shadow quota accounting info when + * the regular quota code are doing the same. + */ +static int +xqcheck_apply_live_dqtrx( + struct notifier_block *nb, + unsigned long arg, + void *data) +{ + struct xfs_apply_dqtrx_params *p = data; + struct xqcheck *xqc; + struct xqcheck_dqacct *dqa; + struct xqcheck_dqtrx *dqtrx; + struct xfarray *counts; + int error; + + xqc = container_of(nb, struct xqcheck, apply_dqtrx_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; + } + + if (xchk_iscan_aborted(&xqc->iscan) || counts == NULL) + goto out_done; + + /* + * Find the shadow dqtrx for this transaction and dquot, if any deltas + * need to be applied here. + */ + mutex_lock(&xqc->lock); + 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 we're committing. */ + if (arg == XFS_APPLY_DQTRX_COMMIT) { + error = xqcheck_update_incore_counts(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); + } + +out_unlock: + mutex_unlock(&xqc->lock); +out_done: + return NOTIFY_DONE; +fail: + xchk_iscan_abort(&xqc->iscan); + goto out_unlock; +} + /* Record this inode's quota usage in our shadow quota counter data. */ STATIC int xqcheck_inode( @@ -119,6 +389,11 @@ xqcheck_inode( } xfs_inode_count_blocks(tp, ip, &nblks, &rtblks); + if (xchk_iscan_aborted(&xqc->iscan)) { + error = -ECANCELED; + goto out_ilock; + } + /* Update the shadow dquot counters. */ mutex_lock(&xqc->lock); id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_USER); @@ -152,6 +427,7 @@ xqcheck_inode( out_incomplete: mutex_unlock(&xqc->lock); xchk_set_incomplete(xqc->sc); + xchk_iscan_abort(&xqc->iscan); out_ilock: xfs_iunlock(ip, XFS_IOLOCK_SHARED | XFS_MMAPLOCK_SHARED | ilock_flags); return error; @@ -233,6 +509,11 @@ xqcheck_compare_dquot( struct xfarray *counts = xqcheck_counters_for(xqc, dqtype); int error; + if (xchk_iscan_aborted(&xqc->iscan)) { + xchk_set_incomplete(xqc->sc); + return -ECANCELED; + } + mutex_lock(&xqc->lock); error = xfarray_load_sparse(counts, dqp->q_id, &xcdq); if (error) @@ -266,7 +547,6 @@ xqcheck_compare_dquot( return -ECANCELED; return 0; - out_unlock: mutex_unlock(&xqc->lock); return error; @@ -351,6 +631,28 @@ 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_abort(&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_ino_dqtrx_hooks, &xqc->mod_dqtrx_hook); + xfs_hook_del(&qi->qi_apply_dqtrx_hooks, &xqc->apply_dqtrx_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) { xfarray_destroy(xqc->pcounts); xqc->pcounts = NULL; @@ -381,6 +683,7 @@ xqcheck_setup_scan( struct xfs_scrub *sc, struct xqcheck *xqc) { + struct xfs_quotainfo *qi = sc->mp->m_quotainfo; int error; ASSERT(xqc->sc == NULL); @@ -413,6 +716,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_dqtrx_hooks, &xqc->apply_dqtrx_hook, + xqcheck_apply_live_dqtrx); + if (error) + goto out_teardown; + error = xfs_hook_add(&qi->qi_mod_ino_dqtrx_hooks, &xqc->mod_dqtrx_hook, + xqcheck_mod_live_ino_dqtrx); + 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 415f7a63418f..238585af5af4 100644 --- a/fs/xfs/scrub/quotacheck.h +++ b/fs/xfs/scrub/quotacheck.h @@ -37,6 +37,13 @@ struct xqcheck { struct mutex lock; struct xchk_iscan iscan; + + /* Hooks into the quota code. */ + struct notifier_block mod_dqtrx_hook; + struct notifier_block apply_dqtrx_hook; + + /* Shadow quota delta tracking structure. */ + struct rhashtable shadow_dquot_acct; }; /* Return the incore counter array for a given quota type. */ diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 89e1a0d1858d..b981d300c0ab 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -691,6 +691,9 @@ xfs_qm_init_quotainfo( if (error) goto out_free_inos; + xfs_hook_init(&qinf->qi_mod_ino_dqtrx_hooks); + xfs_hook_init(&qinf->qi_apply_dqtrx_hooks); + return 0; out_free_inos: @@ -1782,12 +1785,12 @@ xfs_qm_vop_chown( ASSERT(prevdq); ASSERT(prevdq != newdq); - xfs_trans_mod_dquot(tp, prevdq, bfield, -(ip->i_nblocks)); - xfs_trans_mod_dquot(tp, prevdq, XFS_TRANS_DQ_ICOUNT, -1); + xfs_trans_mod_ino_dquot(tp, ip, prevdq, bfield, -(ip->i_nblocks)); + xfs_trans_mod_ino_dquot(tp, ip, prevdq, XFS_TRANS_DQ_ICOUNT, -1); /* the sparkling new dquot */ - xfs_trans_mod_dquot(tp, newdq, bfield, ip->i_nblocks); - xfs_trans_mod_dquot(tp, newdq, XFS_TRANS_DQ_ICOUNT, 1); + xfs_trans_mod_ino_dquot(tp, ip, newdq, bfield, ip->i_nblocks); + xfs_trans_mod_ino_dquot(tp, ip, newdq, XFS_TRANS_DQ_ICOUNT, 1); /* * Back when we made quota reservations for the chown, we reserved the @@ -1869,22 +1872,21 @@ xfs_qm_vop_create_dqattach( ASSERT(i_uid_read(VFS_I(ip)) == udqp->q_id); ip->i_udquot = xfs_qm_dqhold(udqp); - xfs_trans_mod_dquot(tp, udqp, XFS_TRANS_DQ_ICOUNT, 1); } if (gdqp && XFS_IS_GQUOTA_ON(mp)) { ASSERT(ip->i_gdquot == NULL); ASSERT(i_gid_read(VFS_I(ip)) == gdqp->q_id); ip->i_gdquot = xfs_qm_dqhold(gdqp); - xfs_trans_mod_dquot(tp, gdqp, XFS_TRANS_DQ_ICOUNT, 1); } if (pdqp && XFS_IS_PQUOTA_ON(mp)) { ASSERT(ip->i_pdquot == NULL); ASSERT(ip->i_projid == pdqp->q_id); ip->i_pdquot = xfs_qm_dqhold(pdqp); - xfs_trans_mod_dquot(tp, pdqp, XFS_TRANS_DQ_ICOUNT, 1); } + + xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_ICOUNT, 1); } /* Decide if this inode's dquot is near an enforcement boundary. */ diff --git a/fs/xfs/xfs_qm.h b/fs/xfs/xfs_qm.h index 5bb12717ea28..731bfc1ffdb8 100644 --- a/fs/xfs/xfs_qm.h +++ b/fs/xfs/xfs_qm.h @@ -69,6 +69,10 @@ struct xfs_quotainfo { /* Minimum and maximum quota expiration timestamp values. */ time64_t qi_expiry_min; time64_t qi_expiry_max; + + /* online quotacheck stuff */ + struct xfs_hook_chain qi_mod_ino_dqtrx_hooks; + struct xfs_hook_chain qi_apply_dqtrx_hooks; }; static inline struct radix_tree_root * @@ -105,6 +109,17 @@ xfs_quota_inode(struct xfs_mount *mp, xfs_dqtype_t type) return NULL; } +/* + * Parameters for tracking dqtrx changes on behalf of an inode. The hook + * function arg parameter is the field being updated. + */ +struct xfs_mod_ino_dqtrx_params { + struct xfs_trans *tp; + struct xfs_inode *ip; + struct xfs_dquot *dqp; + int64_t delta; +}; + extern void xfs_trans_mod_dquot(struct xfs_trans *tp, struct xfs_dquot *dqp, uint field, int64_t delta); extern void xfs_trans_dqjoin(struct xfs_trans *, struct xfs_dquot *); diff --git a/fs/xfs/xfs_quota.h b/fs/xfs/xfs_quota.h index dcc785fdd345..cff4512381dc 100644 --- a/fs/xfs/xfs_quota.h +++ b/fs/xfs/xfs_quota.h @@ -74,6 +74,20 @@ struct xfs_dqtrx { int64_t qt_icount_delta; /* dquot inode count changes */ }; +enum xfs_apply_dqtrx_type { + XFS_APPLY_DQTRX_COMMIT = 0, + XFS_APPLY_DQTRX_UNRESERVE, +}; + +/* + * Parameters for applying dqtrx changes to a dquot. The hook function arg + * parameter is enum xfs_apply_dqtrx_type. + */ +struct xfs_apply_dqtrx_params { + struct xfs_trans *tp; + struct xfs_dquot *dqp; +}; + #ifdef CONFIG_XFS_QUOTA extern void xfs_trans_dup_dqinfo(struct xfs_trans *, struct xfs_trans *); extern void xfs_trans_free_dqinfo(struct xfs_trans *); @@ -180,4 +194,12 @@ xfs_quota_unreserve_blkres(struct xfs_inode *ip, int64_t blocks) extern int xfs_mount_reset_sbqflags(struct xfs_mount *); +#ifdef CONFIG_XFS_LIVE_HOOKS +extern void xfs_trans_mod_ino_dquot(struct xfs_trans *tp, struct xfs_inode *ip, + struct xfs_dquot *dqp, unsigned int field, int64_t delta); +#else +#define xfs_trans_mod_ino_dquot(tp, ip, dqp, field, delta) \ + xfs_trans_mod_dquot((tp), (dqp), (field), (delta)) +#endif /* CONFIG_XFS_LIVE_HOOKS */ + #endif /* __XFS_QUOTA_H__ */ diff --git a/fs/xfs/xfs_trans_dquot.c b/fs/xfs/xfs_trans_dquot.c index d139daf96cce..2c1711405d4c 100644 --- a/fs/xfs/xfs_trans_dquot.c +++ b/fs/xfs/xfs_trans_dquot.c @@ -121,6 +121,29 @@ xfs_trans_dup_dqinfo( } } +#ifdef CONFIG_XFS_LIVE_HOOKS +/* Schedule a transactional dquot update on behalf of an inode. */ +void +xfs_trans_mod_ino_dquot( + struct xfs_trans *tp, + struct xfs_inode *ip, + struct xfs_dquot *dqp, + unsigned int field, + int64_t delta) +{ + struct xfs_mod_ino_dqtrx_params p = { + .tp = tp, + .ip = ip, + .dqp = dqp, + .delta = delta + }; + struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo; + + xfs_trans_mod_dquot(tp, dqp, field, delta); + xfs_hook_call(&qi->qi_mod_ino_dqtrx_hooks, field, &p); +} +#endif /* CONFIG_XFS_LIVE_HOOKS */ + /* * Wrap around mod_dquot to account for both user and group quotas. */ @@ -138,11 +161,11 @@ xfs_trans_mod_dquot_byino( return; if (XFS_IS_UQUOTA_ON(mp) && ip->i_udquot) - (void) xfs_trans_mod_dquot(tp, ip->i_udquot, field, delta); + xfs_trans_mod_ino_dquot(tp, ip, ip->i_udquot, field, delta); if (XFS_IS_GQUOTA_ON(mp) && ip->i_gdquot) - (void) xfs_trans_mod_dquot(tp, ip->i_gdquot, field, delta); + xfs_trans_mod_ino_dquot(tp, ip, ip->i_gdquot, field, delta); if (XFS_IS_PQUOTA_ON(mp) && ip->i_pdquot) - (void) xfs_trans_mod_dquot(tp, ip->i_pdquot, field, delta); + xfs_trans_mod_ino_dquot(tp, ip, ip->i_pdquot, field, delta); } STATIC struct xfs_dqtrx * @@ -322,6 +345,25 @@ xfs_apply_quota_reservation_deltas( } } +#ifdef CONFIG_XFS_LIVE_HOOKS +/* Call downstream hooks now that it's time to apply dquot deltas. */ +static inline void +xfs_trans_apply_dquot_deltas_hook( + struct xfs_trans *tp, + struct xfs_dquot *dqp) +{ + struct xfs_apply_dqtrx_params p = { + .tp = tp, + .dqp = dqp, + }; + struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo; + + xfs_hook_call(&qi->qi_apply_dqtrx_hooks, XFS_APPLY_DQTRX_COMMIT, &p); +} +#else +# define xfs_trans_apply_dquot_deltas_hook(tp, dqp) +#endif /* CONFIG_XFS_LIVE_HOOKS */ + /* * Called by xfs_trans_commit() and similar in spirit to * xfs_trans_apply_sb_deltas(). @@ -367,6 +409,8 @@ xfs_trans_apply_dquot_deltas( ASSERT(XFS_DQ_IS_LOCKED(dqp)); + xfs_trans_apply_dquot_deltas_hook(tp, dqp); + /* * adjust the actual number of blocks used */ @@ -466,6 +510,25 @@ xfs_trans_apply_dquot_deltas( } } +#ifdef CONFIG_XFS_LIVE_HOOKS +/* Call downstream hooks now that it's time to cancel dquot deltas. */ +static inline void +xfs_trans_unreserve_and_mod_dquots_hook( + struct xfs_trans *tp, + struct xfs_dquot *dqp) +{ + struct xfs_apply_dqtrx_params p = { + .tp = tp, + .dqp = dqp, + }; + struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo; + + xfs_hook_call(&qi->qi_apply_dqtrx_hooks, XFS_APPLY_DQTRX_UNRESERVE, &p); +} +#else +# define xfs_trans_unreserve_and_mod_dquots_hook(tp, dqp) +#endif /* CONFIG_XFS_LIVE_HOOKS */ + /* * Release the reservations, and adjust the dquots accordingly. * This is called only when the transaction is being aborted. If by @@ -496,6 +559,9 @@ xfs_trans_unreserve_and_mod_dquots( */ if ((dqp = qtrx->qt_dquot) == NULL) break; + + xfs_trans_unreserve_and_mod_dquots_hook(tp, dqp); + /* * Unreserve the original reservation. We don't care * about the number of blocks used field, or deltas. |