summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2020-10-25 17:14:39 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2020-10-26 18:32:15 -0700
commited2ad3f8cc3d1b1abeb04a4d77cdabf8d6d650eb (patch)
tree327312adf1df831f2d234b578d38689a381c5063
parentc1cf3b00bef8f1b2085158cd578db2fe887f7747 (diff)
xfs: track quota updates during live quotacheck
Create a shadow dqtrx system in the quotacheck code that hooks the regular dquot counter update code. This will be the means to keep our copy of the dquot counters up to date while the scan runs in real time. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r--fs/xfs/Kconfig1
-rw-r--r--fs/xfs/scrub/quotacheck.c359
-rw-r--r--fs/xfs/scrub/quotacheck.h16
-rw-r--r--fs/xfs/xfs_mount.c43
-rw-r--r--fs/xfs/xfs_mount.h10
-rw-r--r--fs/xfs/xfs_qm.c18
-rw-r--r--fs/xfs/xfs_qm.h15
-rw-r--r--fs/xfs/xfs_quota.h19
-rw-r--r--fs/xfs/xfs_trans_dquot.c72
9 files changed, 536 insertions, 17 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/quotacheck.c b/fs/xfs/scrub/quotacheck.c
index 1cc3cec04d85..6fbab0382ba7 100644
--- a/fs/xfs/scrub/quotacheck.c
+++ b/fs/xfs/scrub/quotacheck.c
@@ -32,15 +32,88 @@
* 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);
+}
/* Tear down everything associated with a quotacheck. */
static void
xqcheck_teardown(
struct xqcheck *xqc)
{
+ struct xfs_quotainfo *qi;
+
if (xqc->sc == NULL)
return;
+ qi = xqc->sc->mp->m_quotainfo;
+
+ /* Discourage any hook functions that might be running. */
+ mutex_lock(&xqc->lock);
+ xqc->hook_dead = true;
+ mutex_unlock(&xqc->lock);
+
+ /*
+ * 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.
+ */
+ 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);
+ memset(&xqc->shadow_dquot_acct, 0, sizeof(struct rhashtable));
+ }
if (xqc->pcounts) {
xfbma_destroy(xqc->pcounts);
@@ -57,6 +130,7 @@ xqcheck_teardown(
xqc->ucounts = NULL;
}
+ mutex_destroy(&xqc->lock);
xqc->sc = NULL;
}
@@ -66,9 +140,6 @@ xchk_setup_quotacheck(
struct xfs_scrub *sc,
struct xfs_inode *ip)
{
- /* Not ready for general consumption yet. */
- return -EOPNOTSUPP;
-
if (!XFS_IS_QUOTA_RUNNING(sc->mp) || !XFS_IS_QUOTA_ON(sc->mp))
return -ENOENT;
@@ -109,7 +180,7 @@ xqcheck_get_shadow_dquot(
return error;
}
-/* Update an incore dquot information. */
+/* Update an incore dquot information. Caller must hold the xqc lock. */
static int
xqcheck_update_incore(
struct xqcheck *xqc,
@@ -146,6 +217,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. */
+ mutex_lock(&xqc->lock);
+ if (xqc->last_ino < p->ip->i_ino || 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:
+ mutex_unlock(&xqc->lock);
+ 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;
+ }
+
+ mutex_lock(&xqc->lock);
+ 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:
+ mutex_unlock(&xqc->lock);
+ return NOTIFY_DONE;
+}
+
/* Record this inode's quota usage in our shadow quota counter data. */
STATIC int
xqcheck_inode(
@@ -178,20 +453,39 @@ xqcheck_inode(
}
nblks = (xfs_qcnt_t)ip->i_d.di_nblocks - rtblks;
- /* Update the shadow dquot counters. */
+ /* Update the shadow dquot counters if we haven't already failed. */
+ mutex_lock(&xqc->lock);
+ if (xqc->hook_dead) {
+ error = -ECANCELED;
+ goto out_xqclock;
+ }
+
id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_USER);
error = xqcheck_update_incore(xqc, xqc->ucounts, id, 1, nblks, rtblks);
if (error)
- goto out_ilock;
+ goto out_xqclock;
id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_GROUP);
error = xqcheck_update_incore(xqc, xqc->gcounts, id, 1, nblks, rtblks);
if (error)
- goto out_ilock;
+ goto out_xqclock;
id = xfs_qm_id_for_quotatype(ip, XFS_DQTYPE_PROJ);
error = xqcheck_update_incore(xqc, xqc->pcounts, id, 1, nblks, rtblks);
+ if (error)
+ goto out_xqclock;
+ /*
+ * Update the quotacheck scan cursor so that the quota hooks will
+ * capture any quota updates made on behalf of this inode after we
+ * unlock it.
+ */
+ xqc->last_ino = ip->i_ino;
+
+out_xqclock:
+ if (error)
+ xqc->hook_dead = true;
+ mutex_unlock(&xqc->lock);
out_ilock:
xfs_iunlock(ip, ilock_flags);
return error;
@@ -226,6 +520,15 @@ next_ag:
next_ino:
error = xfs_iwalk_find_next(sc->mp, sc->tp, agi_bp, ino);
+
+ /*
+ * Update the quotacheck scan cursor so that the quota hooks will begin
+ * to capture quota updates being made by ongoing transactions.
+ */
+ mutex_lock(&xqc->lock);
+ xqc->last_ino = *ino - 1;
+ mutex_unlock(&xqc->lock);
+
if (error || *ino == NULLFSINO) {
xfs_trans_brelse(sc->tp, agi_bp);
if (error == -EAGAIN)
@@ -296,6 +599,11 @@ xqcheck_compare_dquot(
struct xfbma *counts = xqcheck_counters_for(xqc, dqtype);
int error;
+ mutex_lock(&xqc->lock);
+ if (xqc->hook_dead) {
+ error = -ECANCELED;
+ goto out_unlock;
+ }
error = xqcheck_get_shadow_dquot(counts, dqp->q_id, &xcdq);
if (error)
goto out_unlock;
@@ -315,6 +623,7 @@ xqcheck_compare_dquot(
}
out_unlock:
+ mutex_unlock(&xqc->lock);
return error;
}
@@ -336,9 +645,12 @@ xqcheck_walk_observations(
if (!counts)
return 0;
+ mutex_lock(&xqc->lock);
while (!(error = xfbma_iter_get(counts, &nr, &xcdq))) {
xfs_dqid_t id = nr - 1;
+ mutex_unlock(&xqc->lock);
+
if (xchk_should_terminate(xqc->sc, &error))
return error;
@@ -357,7 +669,9 @@ xqcheck_walk_observations(
if (error)
return error;
+ mutex_lock(&xqc->lock);
}
+ mutex_unlock(&xqc->lock);
/*
* ENODATA means we hit the end of the array; ECANCELED means we
@@ -409,11 +723,14 @@ 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;
+ mutex_init(&xqc->lock);
error = -ENOMEM;
if (xfs_this_quota_on(sc->mp, XFS_DQTYPE_USER)) {
@@ -437,6 +754,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;
+
/* Walk all inodes, picking up quota information. */
error = xqcheck_iwalk(xqc);
if (error)
diff --git a/fs/xfs/scrub/quotacheck.h b/fs/xfs/scrub/quotacheck.h
index 4ed8c6129e86..91b69ecf4bbf 100644
--- a/fs/xfs/scrub/quotacheck.h
+++ b/fs/xfs/scrub/quotacheck.h
@@ -30,6 +30,22 @@ struct xqcheck {
struct xfbma *ucounts;
struct xfbma *gcounts;
struct xfbma *pcounts;
+
+ /* Last inode scanned by live quotacheck. */
+ xfs_ino_t last_ino;
+
+ /* Hooks into the quota code. */
+ struct notifier_block mod_hook;
+ struct notifier_block apply_hook;
+
+ /* Lock for the data used to capture live quota counter updates. */
+ struct mutex lock;
+
+ /* 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 1a1e61d65f17..e9ca5b35bb78 100644
--- a/fs/xfs/xfs_mount.c
+++ b/fs/xfs/xfs_mount.c
@@ -1392,3 +1392,46 @@ clamp:
/* Don't return an estimate larger than the CPU count. */
return min(num_online_cpus(), threads);
}
+
+/* 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 70f6c68c795f..8c3b0d52f613 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -428,4 +428,14 @@ void xfs_force_summary_recalc(struct xfs_mount *mp);
void xfs_mod_delalloc(struct xfs_mount *mp, int64_t delta);
unsigned int xfs_guess_metadata_threads(struct xfs_mount *mp);
+struct xfs_hook_chain {
+ struct srcu_notifier_head head;
+};
+
+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 b2a9abee8b2b..ef85b7c5266a 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -694,6 +694,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:
@@ -1778,12 +1783,12 @@ xfs_qm_vop_chown(
ASSERT(prevdq);
ASSERT(prevdq != newdq);
- xfs_trans_mod_dquot(tp, prevdq, bfield, -(ip->i_d.di_nblocks));
- xfs_trans_mod_dquot(tp, prevdq, XFS_TRANS_DQ_ICOUNT, -1);
+ xfs_trans_mod_ino_dquot(tp, ip, prevdq, bfield, -(ip->i_d.di_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_d.di_nblocks);
- xfs_trans_mod_dquot(tp, newdq, XFS_TRANS_DQ_ICOUNT, 1);
+ xfs_trans_mod_ino_dquot(tp, ip, newdq, bfield, ip->i_d.di_nblocks);
+ xfs_trans_mod_ino_dquot(tp, ip, newdq, XFS_TRANS_DQ_ICOUNT, 1);
/*
* Take an extra reference, because the inode is going to keep
@@ -1935,21 +1940,20 @@ 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_d.di_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);
}
diff --git a/fs/xfs/xfs_qm.h b/fs/xfs/xfs_qm.h
index e3dabab44097..66d70bc19649 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 5a62398940d0..fa6477347ffc 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 *);
@@ -151,6 +160,16 @@ static inline int xfs_trans_reserve_quota_bydquots(struct xfs_trans *tp,
#define xfs_qm_unmount_quotas(mp)
#endif /* CONFIG_XFS_QUOTA */
+#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
+
#define xfs_trans_unreserve_quota_nblks(tp, ip, nblks, ninos, flags) \
xfs_trans_reserve_quota_nblks(tp, ip, -(nblks), -(ninos), flags)
#define xfs_trans_reserve_quota(tp, mp, ud, gd, pd, nb, ni, f) \
diff --git a/fs/xfs/xfs_trans_dquot.c b/fs/xfs/xfs_trans_dquot.c
index fe45b0c3970c..d8c4a1b03b48 100644
--- a/fs/xfs/xfs_trans_dquot.c
+++ b/fs/xfs/xfs_trans_dquot.c
@@ -126,6 +126,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.
*/
@@ -147,11 +172,11 @@ xfs_trans_mod_dquot_byino(
xfs_trans_alloc_dqinfo(tp);
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 *
@@ -333,6 +358,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().
@@ -378,6 +421,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
*/
@@ -477,6 +522,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
@@ -507,6 +570,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.