diff options
Diffstat (limited to 'fs/xfs')
-rw-r--r-- | fs/xfs/Kconfig | 1 | ||||
-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 | ||||
-rw-r--r-- | fs/xfs/xfs_mount.c | 43 | ||||
-rw-r--r-- | fs/xfs/xfs_mount.h | 10 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.c | 18 | ||||
-rw-r--r-- | fs/xfs/xfs_qm.h | 15 | ||||
-rw-r--r-- | fs/xfs/xfs_quota.h | 19 | ||||
-rw-r--r-- | fs/xfs/xfs_trans_dquot.c | 72 |
10 files changed, 506 insertions, 15 deletions
diff --git a/fs/xfs/Kconfig b/fs/xfs/Kconfig index 7f12b40146b3..85d5de6dc2a6 100644 --- a/fs/xfs/Kconfig +++ b/fs/xfs/Kconfig @@ -98,6 +98,7 @@ config XFS_ONLINE_SCRUB default n depends on XFS_FS depends on TMPFS && SHMEM + depends on SRCU help If you say Y here you will be able to check metadata on a mounted XFS filesystem. This feature is intended to reduce 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. */ diff --git a/fs/xfs/xfs_mount.c b/fs/xfs/xfs_mount.c index 06dac09eddbd..45b22525b2bf 100644 --- a/fs/xfs/xfs_mount.c +++ b/fs/xfs/xfs_mount.c @@ -1365,3 +1365,46 @@ xfs_mod_delalloc( percpu_counter_add_batch(&mp->m_delalloc_blks, delta, XFS_DELALLOC_BATCH); } + +/* Initialize a hook. */ +void +xfs_hook_init( + struct xfs_hook_chain *chain) +{ + srcu_init_notifier_head(&chain->head); +} + +/* Make it so a function gets called whenever we hit a certain hook point. */ +int +xfs_hook_add( + struct xfs_hook_chain *chain, + struct notifier_block *hook, + notifier_fn_t fn) +{ + hook->notifier_call = fn; + return srcu_notifier_chain_register(&chain->head, hook); +} + +/* Remove a previously installed hook. */ +void +xfs_hook_del( + struct xfs_hook_chain *chain, + struct notifier_block *hook) +{ + if (!hook->notifier_call) + return; + + srcu_notifier_chain_unregister(&chain->head, hook); + rcu_barrier(); + hook->notifier_call = NULL; +} + +/* Call a hook. */ +int +xfs_hook_call( + struct xfs_hook_chain *chain, + unsigned long val, + void *priv) +{ + return srcu_notifier_call_chain(&chain->head, val, priv); +} diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index e091f3b3fa15..0bc50069254c 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -56,6 +56,10 @@ struct xfs_error_cfg { long retry_timeout; /* in jiffies, -1 = infinite */ }; +struct xfs_hook_chain { + struct srcu_notifier_head head; +}; + /* * Per-cpu deferred inode inactivation GC lists. */ @@ -501,4 +505,10 @@ int xfs_add_incompat_log_feature(struct xfs_mount *mp, uint32_t feature); bool xfs_clear_incompat_log_features(struct xfs_mount *mp); void xfs_mod_delalloc(struct xfs_mount *mp, int64_t delta); +void xfs_hook_init(struct xfs_hook_chain *chain); +int xfs_hook_add(struct xfs_hook_chain *chain, struct notifier_block *hook, + notifier_fn_t fn); +void xfs_hook_del(struct xfs_hook_chain *chain, struct notifier_block *hook); +int xfs_hook_call(struct xfs_hook_chain *chain, unsigned long val, void *priv); + #endif /* __XFS_MOUNT_H__ */ diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 5b4e97a56116..d03ec51660e0 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -691,6 +691,11 @@ xfs_qm_init_quotainfo( if (error) goto out_free_inos; +#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) + xfs_hook_init(&qinf->qi_mod_dquot_hooks); + xfs_hook_init(&qinf->qi_apply_dquot_deltas_hooks); +#endif + return 0; out_free_inos: @@ -1782,12 +1787,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 +1874,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 442a0f97a9d4..980bd2ef186d 100644 --- a/fs/xfs/xfs_qm.h +++ b/fs/xfs/xfs_qm.h @@ -69,6 +69,12 @@ struct xfs_quotainfo { /* Minimum and maximum quota expiration timestamp values. */ time64_t qi_expiry_min; time64_t qi_expiry_max; + +#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) + /* online quotacheck stuff */ + struct xfs_hook_chain qi_mod_dquot_hooks; + struct xfs_hook_chain qi_apply_dquot_deltas_hooks; +#endif }; static inline struct radix_tree_root * @@ -105,6 +111,15 @@ xfs_quota_inode(struct xfs_mount *mp, xfs_dqtype_t type) return NULL; } +/* Parameters for xfs_trans_mod_dquot hook. */ +struct xfs_trans_mod_dquot_params { + struct xfs_trans *tp; + struct xfs_inode *ip; + struct xfs_dquot *dqp; + uint field; + 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..e736231a33a3 100644 --- a/fs/xfs/xfs_quota.h +++ b/fs/xfs/xfs_quota.h @@ -74,6 +74,15 @@ struct xfs_dqtrx { int64_t qt_icount_delta; /* dquot inode count changes */ }; +/* + * Parameters for xfs_trans_apply_dquot_deltas hook. The hook arg parameter + * is 1 to apply and 0 to cancel the update. + */ +struct xfs_trans_apply_dquot_deltas_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 +189,14 @@ xfs_quota_unreserve_blkres(struct xfs_inode *ip, int64_t blocks) extern int xfs_mount_reset_sbqflags(struct xfs_mount *); +#if IS_ENABLED(CONFIG_XFS_QUOTA) && IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) +extern void xfs_trans_mod_ino_dquot(struct xfs_trans *tp, struct xfs_inode *ip, + struct xfs_dquot *dqp, uint field, int64_t delta); +#elif IS_ENABLED(CONFIG_XFS_QUOTA) +# define xfs_trans_mod_ino_dquot(tp, ip, dqp, field, delta) \ + xfs_trans_mod_dquot((tp), (dqp), (field), (delta)) +#else +# define xfs_trans_mod_ino_dquot(tp, ip, dqp, field, delta) +#endif + #endif /* __XFS_QUOTA_H__ */ diff --git a/fs/xfs/xfs_trans_dquot.c b/fs/xfs/xfs_trans_dquot.c index 90a8d47e41d5..5010160f766d 100644 --- a/fs/xfs/xfs_trans_dquot.c +++ b/fs/xfs/xfs_trans_dquot.c @@ -121,6 +121,31 @@ xfs_trans_dup_dqinfo( } } +/* Schedule a transactional dquot update on behalf of an inode. */ +#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) +void +xfs_trans_mod_ino_dquot( + struct xfs_trans *tp, + struct xfs_inode *ip, + struct xfs_dquot *dqp, + uint field, + int64_t delta) +{ + struct xfs_trans_mod_dquot_params p; + struct xfs_quotainfo *qi; + + xfs_trans_mod_dquot(tp, dqp, field, delta); + + p.tp = tp; + p.ip = ip; + p.dqp = dqp; + p.field = field; + p.delta = delta; + qi = tp->t_mountp->m_quotainfo; + xfs_hook_call(&qi->qi_mod_dquot_hooks, 0, &p); +} +#endif + /* * Wrap around mod_dquot to account for both user and group quotas. */ @@ -138,11 +163,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 +347,24 @@ xfs_apply_quota_reservation_deltas( } } +/* Call downstream hooks now that it's time to apply dquot deltas. */ +#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) +static inline void +xfs_trans_apply_dquot_deltas_hook( + struct xfs_trans *tp, + struct xfs_dquot *dqp) +{ + struct xfs_trans_apply_dquot_deltas_params p; + struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo; + + p.tp = tp; + p.dqp = dqp; + xfs_hook_call(&qi->qi_apply_dquot_deltas_hooks, 1, &p); +} +#else +# define xfs_trans_apply_dquot_deltas_hook(tp, dqp) +#endif + /* * Called by xfs_trans_commit() and similar in spirit to * xfs_trans_apply_sb_deltas(). @@ -367,6 +410,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 +511,24 @@ xfs_trans_apply_dquot_deltas( } } +/* Call downstream hooks now that it's time to cancel dquot deltas. */ +#if IS_ENABLED(CONFIG_XFS_ONLINE_SCRUB) +static inline void +xfs_trans_unreserve_and_mod_dquots_hook( + struct xfs_trans *tp, + struct xfs_dquot *dqp) +{ + struct xfs_trans_apply_dquot_deltas_params p; + struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo; + + p.tp = tp; + p.dqp = dqp; + xfs_hook_call(&qi->qi_apply_dquot_deltas_hooks, 0, &p); +} +#else +# define xfs_trans_unreserve_and_mod_dquots_hook(tp, dqp) +#endif + /* * 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. |