Commit 20049187 authored by Darrick J. Wong's avatar Darrick J. Wong

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: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent 48dd9117
......@@ -30,6 +30,7 @@
#include "xfs_reflink.h"
#include "xfs_ag.h"
#include "xfs_error.h"
#include "xfs_quota.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
......@@ -1298,6 +1299,9 @@ xchk_fsgates_enable(
if (scrub_fsgates & XCHK_FSGATES_DRAIN)
xfs_drain_wait_enable();
if (scrub_fsgates & XCHK_FSGATES_QUOTA)
xfs_dqtrx_hook_enable();
sc->flags |= scrub_fsgates;
}
......
This diff is collapsed.
......@@ -43,6 +43,12 @@ struct xqcheck {
struct mutex lock;
struct xchk_iscan iscan;
/* Hooks into the quota code. */
struct xfs_dqtrx_hook qhook;
/* Shadow quota delta tracking structure. */
struct rhashtable shadow_dquot_acct;
};
/* Return the incore counter array for a given quota type. */
......
......@@ -157,6 +157,9 @@ xchk_fsgates_disable(
if (sc->flags & XCHK_FSGATES_DRAIN)
xfs_drain_wait_disable();
if (sc->flags & XCHK_FSGATES_QUOTA)
xfs_dqtrx_hook_disable();
sc->flags &= ~XCHK_FSGATES_ALL;
}
......
......@@ -121,6 +121,7 @@ struct xfs_scrub {
#define XCHK_HAVE_FREEZE_PROT (1U << 1) /* do we have freeze protection? */
#define XCHK_FSGATES_DRAIN (1U << 2) /* defer ops draining enabled */
#define XCHK_NEED_DRAIN (1U << 3) /* scrub needs to drain defer ops */
#define XCHK_FSGATES_QUOTA (1U << 4) /* quota live update enabled */
#define XREP_RESET_PERAG_RESV (1U << 30) /* must reset AG space reservation */
#define XREP_ALREADY_FIXED (1U << 31) /* checking our repair work */
......@@ -130,7 +131,8 @@ struct xfs_scrub {
* features are gated off via dynamic code patching, which is why the state
* must be enabled during scrub setup and can only be torn down afterwards.
*/
#define XCHK_FSGATES_ALL (XCHK_FSGATES_DRAIN)
#define XCHK_FSGATES_ALL (XCHK_FSGATES_DRAIN | \
XCHK_FSGATES_QUOTA)
/* Metadata scrubbers */
int xchk_tester(struct xfs_scrub *sc);
......
......@@ -112,6 +112,7 @@ TRACE_DEFINE_ENUM(XFS_SCRUB_TYPE_QUOTACHECK);
{ XCHK_HAVE_FREEZE_PROT, "nofreeze" }, \
{ XCHK_FSGATES_DRAIN, "fsgates_drain" }, \
{ XCHK_NEED_DRAIN, "need_drain" }, \
{ XCHK_FSGATES_QUOTA, "fsgates_quota" }, \
{ XREP_RESET_PERAG_RESV, "reset_perag_resv" }, \
{ XREP_ALREADY_FIXED, "already_fixed" }
......
......@@ -693,6 +693,9 @@ xfs_qm_init_quotainfo(
shrinker_register(qinf->qi_shrinker);
xfs_hooks_init(&qinf->qi_mod_ino_dqtrx_hooks);
xfs_hooks_init(&qinf->qi_apply_dqtrx_hooks);
return 0;
out_free_inos:
......@@ -1824,12 +1827,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
......@@ -1911,22 +1914,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. */
......
......@@ -68,6 +68,10 @@ struct xfs_quotainfo {
/* Minimum and maximum quota expiration timestamp values. */
time64_t qi_expiry_min;
time64_t qi_expiry_max;
/* Hook to feed quota counter updates to an active online repair. */
struct xfs_hooks qi_mod_ino_dqtrx_hooks;
struct xfs_hooks qi_apply_dqtrx_hooks;
};
static inline struct radix_tree_root *
......@@ -104,6 +108,18 @@ 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 {
uintptr_t tx_id;
xfs_ino_t ino;
xfs_dqtype_t q_type;
xfs_dqid_t q_id;
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 *);
......
......@@ -9,6 +9,7 @@
#include "xfs_format.h"
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
#include "xfs_quota.h"
#include "xfs_mount.h"
#include "xfs_inode.h"
......
......@@ -74,6 +74,22 @@ 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 {
uintptr_t tx_id;
xfs_ino_t ino;
xfs_dqtype_t q_type;
xfs_dqid_t q_id;
};
#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 *);
......@@ -114,6 +130,30 @@ xfs_quota_reserve_blkres(struct xfs_inode *ip, int64_t blocks)
return xfs_trans_reserve_quota_nblks(NULL, ip, blocks, 0, false);
}
bool xfs_inode_near_dquot_enforcement(struct xfs_inode *ip, xfs_dqtype_t type);
# ifdef CONFIG_XFS_LIVE_HOOKS
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_quotainfo;
struct xfs_dqtrx_hook {
struct xfs_hook mod_hook;
struct xfs_hook apply_hook;
};
void xfs_dqtrx_hook_disable(void);
void xfs_dqtrx_hook_enable(void);
int xfs_dqtrx_hook_add(struct xfs_quotainfo *qi, struct xfs_dqtrx_hook *hook);
void xfs_dqtrx_hook_del(struct xfs_quotainfo *qi, struct xfs_dqtrx_hook *hook);
void xfs_dqtrx_hook_setup(struct xfs_dqtrx_hook *hook, notifier_fn_t mod_fn,
notifier_fn_t apply_fn);
# 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 */
#else
static inline int
xfs_qm_vop_dqalloc(struct xfs_inode *ip, kuid_t kuid, kgid_t kgid,
......@@ -173,6 +213,12 @@ xfs_trans_reserve_quota_icreate(struct xfs_trans *tp, struct xfs_dquot *udqp,
#define xfs_qm_unmount(mp)
#define xfs_qm_unmount_quotas(mp)
#define xfs_inode_near_dquot_enforcement(ip, type) (false)
# ifdef CONFIG_XFS_LIVE_HOOKS
# define xfs_dqtrx_hook_enable() ((void)0)
# define xfs_dqtrx_hook_disable() ((void)0)
# endif /* CONFIG_XFS_LIVE_HOOKS */
#endif /* CONFIG_XFS_QUOTA */
static inline int
......
......@@ -121,6 +121,116 @@ xfs_trans_dup_dqinfo(
}
}
#ifdef CONFIG_XFS_LIVE_HOOKS
/*
* Use a static key here to reduce the overhead of quota live updates. If the
* compiler supports jump labels, the static branch will be replaced by a nop
* sled when there are no hook users. Online fsck is currently the only
* caller, so this is a reasonable tradeoff.
*
* Note: Patching the kernel code requires taking the cpu hotplug lock. Other
* parts of the kernel allocate memory with that lock held, which means that
* XFS callers cannot hold any locks that might be used by memory reclaim or
* writeback when calling the static_branch_{inc,dec} functions.
*/
DEFINE_STATIC_XFS_HOOK_SWITCH(xfs_dqtrx_hooks_switch);
void
xfs_dqtrx_hook_disable(void)
{
xfs_hooks_switch_off(&xfs_dqtrx_hooks_switch);
}
void
xfs_dqtrx_hook_enable(void)
{
xfs_hooks_switch_on(&xfs_dqtrx_hooks_switch);
}
/* 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)
{
xfs_trans_mod_dquot(tp, dqp, field, delta);
if (xfs_hooks_switched_on(&xfs_dqtrx_hooks_switch)) {
struct xfs_mod_ino_dqtrx_params p = {
.tx_id = (uintptr_t)tp,
.ino = ip->i_ino,
.q_type = xfs_dquot_type(dqp),
.q_id = dqp->q_id,
.delta = delta
};
struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo;
xfs_hooks_call(&qi->qi_mod_ino_dqtrx_hooks, field, &p);
}
}
/* Call the specified functions during a dquot counter update. */
int
xfs_dqtrx_hook_add(
struct xfs_quotainfo *qi,
struct xfs_dqtrx_hook *hook)
{
int error;
/*
* Transactional dquot updates first call the mod hook when changes
* are attached to the transaction and then call the apply hook when
* those changes are committed (or canceled).
*
* The apply hook must be installed before the mod hook so that we
* never fail to catch the end of a quota update sequence.
*/
error = xfs_hooks_add(&qi->qi_apply_dqtrx_hooks, &hook->apply_hook);
if (error)
goto out;
error = xfs_hooks_add(&qi->qi_mod_ino_dqtrx_hooks, &hook->mod_hook);
if (error)
goto out_apply;
return 0;
out_apply:
xfs_hooks_del(&qi->qi_apply_dqtrx_hooks, &hook->apply_hook);
out:
return error;
}
/* Stop calling the specified function during a dquot counter update. */
void
xfs_dqtrx_hook_del(
struct xfs_quotainfo *qi,
struct xfs_dqtrx_hook *hook)
{
/*
* The mod hook must be removed before apply hook to avoid giving the
* hook consumer with an incomplete update. No hooks should be running
* after these functions return.
*/
xfs_hooks_del(&qi->qi_mod_ino_dqtrx_hooks, &hook->mod_hook);
xfs_hooks_del(&qi->qi_apply_dqtrx_hooks, &hook->apply_hook);
}
/* Configure dquot update hook functions. */
void
xfs_dqtrx_hook_setup(
struct xfs_dqtrx_hook *hook,
notifier_fn_t mod_fn,
notifier_fn_t apply_fn)
{
xfs_hook_setup(&hook->mod_hook, mod_fn);
xfs_hook_setup(&hook->apply_hook, apply_fn);
}
#endif /* CONFIG_XFS_LIVE_HOOKS */
/*
* Wrap around mod_dquot to account for both user and group quotas.
*/
......@@ -138,11 +248,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 +432,29 @@ 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)
{
if (xfs_hooks_switched_on(&xfs_dqtrx_hooks_switch)) {
struct xfs_apply_dqtrx_params p = {
.tx_id = (uintptr_t)tp,
.q_type = xfs_dquot_type(dqp),
.q_id = dqp->q_id,
};
struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo;
xfs_hooks_call(&qi->qi_apply_dqtrx_hooks,
XFS_APPLY_DQTRX_COMMIT, &p);
}
}
#else
# define xfs_trans_apply_dquot_deltas_hook(tp, dqp) ((void)0)
#endif /* CONFIG_XFS_LIVE_HOOKS */
/*
* Called by xfs_trans_commit() and similar in spirit to
* xfs_trans_apply_sb_deltas().
......@@ -367,6 +500,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 +601,29 @@ 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)
{
if (xfs_hooks_switched_on(&xfs_dqtrx_hooks_switch)) {
struct xfs_apply_dqtrx_params p = {
.tx_id = (uintptr_t)tp,
.q_type = xfs_dquot_type(dqp),
.q_id = dqp->q_id,
};
struct xfs_quotainfo *qi = tp->t_mountp->m_quotainfo;
xfs_hooks_call(&qi->qi_apply_dqtrx_hooks,
XFS_APPLY_DQTRX_UNRESERVE, &p);
}
}
#else
# define xfs_trans_unreserve_and_mod_dquots_hook(tp, dqp) ((void)0)
#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 +654,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.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment