[PATCH 10/11] xfs: enable bigtime for quota timers

Darrick J. Wong
2020-08-17T22:57:59Z

From: Darrick J. Wong <darrick.wong@oracle.com>

Enable the bigtime feature for quota timers. We decrease the accuracy
of the timers to ~4s in exchange for being able to set timers up to the
bigtime maximum.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
fs/xfs/libxfs/xfs_dquot_buf.c | 40 ++++++++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_format.h | 26 +++++++++++++++++++++++++-
fs/xfs/libxfs/xfs_quota_defs.h | 3 ++-
fs/xfs/scrub/quota.c | 8 ++++++++
fs/xfs/xfs_dquot.c | 34 ++++++++++++++++++++++++++++++----
fs/xfs/xfs_dquot.h | 3 ++-
fs/xfs/xfs_ondisk.h | 7 +++++++
fs/xfs/xfs_qm.c | 2 ++
fs/xfs/xfs_qm_syscalls.c | 9 +++++----
9 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c
index 7f5291022b11..39afefd52275 100644
--- a/fs/xfs/libxfs/xfs_dquot_buf.c
+++ b/fs/xfs/libxfs/xfs_dquot_buf.c
@@ -69,6 +69,13 @@ xfs_dquot_verify(
ddq_type != XFS_DQTYPE_GROUP)
return __this_address;

+ if ((ddq->d_type & XFS_DQTYPE_BIGTIME) &&
+ !xfs_sb_version_hasbigtime(&mp->m_sb))
+ return __this_address;
+
+ if ((ddq->d_type & XFS_DQTYPE_BIGTIME) && !ddq->d_id)
+ return __this_address;
+
if (id != -1 && id != be32_to_cpu(ddq->d_id))
return __this_address;

@@ -296,6 +303,20 @@ xfs_dquot_from_disk_timestamp(
time64_t *timer,
__be32 dtimer)
{
+ /* Zero always means zero, regardless of encoding. */
+ if (!dtimer) {
+ *timer = 0;
+ return;
+ }
+
+ if (ddq->d_type & XFS_DQTYPE_BIGTIME) {
+ uint64_t t;
+
+ t = be32_to_cpu(dtimer);
+ *timer = t << XFS_DQ_BIGTIME_SHIFT;
+ return;
+ }
+
*timer = be32_to_cpu(dtimer);
}

@@ -306,5 +327,24 @@ xfs_dquot_to_disk_timestamp(
__be32 *dtimer,
time64_t timer)
{
+ /* Zero always means zero, regardless of encoding. */
+ if (!timer) {
+ *dtimer = cpu_to_be32(0);
+ return;
+ }
+
+ if (dqp->q_type & XFS_DQTYPE_BIGTIME) {
+ uint64_t t = timer;
+
+ /*
+ * Round the end of the grace period up to the nearest bigtime
+ * interval that we support, to give users the most time to fix
+ * the problems.
+ */
+ t = roundup_64(t, 1U << XFS_DQ_BIGTIME_SHIFT);
+ *dtimer = cpu_to_be32(t >> XFS_DQ_BIGTIME_SHIFT);
+ return;
+ }
+
*dtimer = cpu_to_be32(timer);
}
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 57343973e5e5..1214fa58f45a 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -1227,13 +1227,15 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)
#define XFS_DQTYPE_USER 0x01 /* user dquot record */
#define XFS_DQTYPE_PROJ 0x02 /* project dquot record */
#define XFS_DQTYPE_GROUP 0x04 /* group dquot record */
+#define XFS_DQTYPE_BIGTIME 0x08 /* large expiry timestamps */

/* bitmask to determine if this is a user/group/project dquot */
#define XFS_DQTYPE_REC_MASK (XFS_DQTYPE_USER | \
XFS_DQTYPE_PROJ | \
XFS_DQTYPE_GROUP)

-#define XFS_DQTYPE_ANY (XFS_DQTYPE_REC_MASK)
+#define XFS_DQTYPE_ANY (XFS_DQTYPE_REC_MASK | \
+ XFS_DQTYPE_BIGTIME)

/*
* XFS Quota Timers
@@ -1270,6 +1272,28 @@ static inline void xfs_dinode_put_rdev(struct xfs_dinode *dip, xfs_dev_t rdev)
#define XFS_DQ_GRACE_MIN ((int64_t)0)
#define XFS_DQ_GRACE_MAX ((int64_t)U32_MAX)

+/*
+ * When bigtime is enabled, we trade a few bits of precision to expand the
+ * expiration timeout range to match that of big inode timestamps. The grace
+ * periods stored in dquot 0 are not shifted, since they record an interval,
+ * not a timestamp.
+ */
+#define XFS_DQ_BIGTIME_SHIFT (2)
+
+/*
+ * Smallest possible quota expiration with big timestamps, which is
+ * Jan 1 00:00:01 UTC 1970.
+ */
+#define XFS_DQ_BIGTIMEOUT_MIN (XFS_DQ_TIMEOUT_MIN)
+
+/*
+ * Largest supported quota expiration with traditional timestamps, which is
+ * the largest bigtime inode timestamp, or Jul 2 20:20:25 UTC 2486. The field
+ * is large enough that it's possible to fit expirations up to 2514, but we
+ * want to keep the maximum timestamp in sync.
+ */
+#define XFS_DQ_BIGTIMEOUT_MAX (XFS_INO_BIGTIME_MAX)
+
/*
* This is the main portion of the on-disk representation of quota information
* for a user. We pad this with some more expansion room to construct the on
diff --git a/fs/xfs/libxfs/xfs_quota_defs.h b/fs/xfs/libxfs/xfs_quota_defs.h
index b524059faab5..b9a47eae684b 100644
--- a/fs/xfs/libxfs/xfs_quota_defs.h
+++ b/fs/xfs/libxfs/xfs_quota_defs.h
@@ -23,7 +23,8 @@ typedef uint8_t xfs_dqtype_t;
#define XFS_DQTYPE_STRINGS \
{ XFS_DQTYPE_USER, "USER" }, \
{ XFS_DQTYPE_PROJ, "PROJ" }, \
- { XFS_DQTYPE_GROUP, "GROUP" }
+ { XFS_DQTYPE_GROUP, "GROUP" }, \
+ { XFS_DQTYPE_BIGTIME, "BIGTIME" }

/*
* flags for q_flags field in the dquot.
diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c
index e34ca20ae8e4..34b51989d8bc 100644
--- a/fs/xfs/scrub/quota.c
+++ b/fs/xfs/scrub/quota.c
@@ -97,6 +97,14 @@ xchk_quota_item(

sqi->last_id = dq->q_id;

+ /*
+ * If the bigtime feature is enabled, all non-root incore dquots should
+ * have the bigtime dquot flag set.
+ */
+ if (xfs_sb_version_hasbigtime(&mp->m_sb) && dq->q_id &&
+ !(dq->q_type & XFS_DQTYPE_BIGTIME))
+ xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset);
+
/*
* Warn if the hard limits are larger than the fs.
* Administrators can do this, though in production this seems
diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c
index 08d497d413b9..8b3ee4baab48 100644
--- a/fs/xfs/xfs_dquot.c
+++ b/fs/xfs/xfs_dquot.c
@@ -110,9 +110,15 @@ xfs_dquot_set_grace_period(
/* Set the expiration time of a quota's grace period. */
void
xfs_dquot_set_timeout(
+ struct xfs_mount *mp,
time64_t *timer,
time64_t value)
{
+ if (xfs_sb_version_hasbigtime(&mp->m_sb)) {
+ *timer = clamp_t(time64_t, value, XFS_DQ_BIGTIMEOUT_MIN,
+ XFS_DQ_BIGTIMEOUT_MAX);
+ return;
+ }
*timer = clamp_t(time64_t, value, XFS_DQ_TIMEOUT_MIN,
XFS_DQ_TIMEOUT_MAX);
}
@@ -123,6 +129,7 @@ xfs_dquot_set_timeout(
*/
static inline void
xfs_qm_adjust_res_timer(
+ struct xfs_dquot *dqp,
struct xfs_dquot_res *res,
struct xfs_quota_limits *qlim)
{
@@ -131,7 +138,7 @@ xfs_qm_adjust_res_timer(
if ((res->softlimit && res->count > res->softlimit) ||
(res->hardlimit && res->count > res->hardlimit)) {
if (res->timer == 0)
- xfs_dquot_set_timeout(&res->timer,
+ xfs_dquot_set_timeout(dqp->q_mount, &res->timer,
ktime_get_real_seconds() + qlim->time);
} else {
if (res->timer == 0)
@@ -165,9 +172,9 @@ xfs_qm_adjust_dqtimers(
ASSERT(dq->q_id);
defq = xfs_get_defquota(qi, xfs_dquot_type(dq));

- xfs_qm_adjust_res_timer(&dq->q_blk, &defq->blk);
- xfs_qm_adjust_res_timer(&dq->q_ino, &defq->ino);
- xfs_qm_adjust_res_timer(&dq->q_rtb, &defq->rtb);
+ xfs_qm_adjust_res_timer(dq, &dq->q_blk, &defq->blk);
+ xfs_qm_adjust_res_timer(dq, &dq->q_ino, &defq->ino);
+ xfs_qm_adjust_res_timer(dq, &dq->q_rtb, &defq->rtb);
}

/*
@@ -221,6 +228,8 @@ xfs_qm_init_dquot_blk(
d->dd_diskdq.d_version = XFS_DQUOT_VERSION;
d->dd_diskdq.d_id = cpu_to_be32(curid);
d->dd_diskdq.d_type = type;
+ if (curid > 0 && xfs_sb_version_hasbigtime(&mp->m_sb))
+ d->dd_diskdq.d_type |= XFS_DQTYPE_BIGTIME;
if (xfs_sb_version_hascrc(&mp->m_sb)) {
uuid_copy(&d->dd_uuid, &mp->m_sb.sb_meta_uuid);
xfs_update_cksum((char *)d, sizeof(struct xfs_dqblk),
@@ -538,6 +547,14 @@ xfs_dquot_from_disk(
xfs_dquot_from_disk_timestamp(ddqp, &dqp->q_ino.timer, ddqp->d_itimer);
xfs_dquot_from_disk_timestamp(ddqp, &dqp->q_rtb.timer, ddqp->d_rtbtimer);

+ /*
+ * Set the bigtime flag incore so that we automatically convert this
+ * dquot's ondisk timestamps to bigtime format the next time we write
+ * the dquot to disk.
+ */
+ if (xfs_sb_version_hasbigtime(&dqp->q_mount->m_sb) && dqp->q_id != 0)
+ dqp->q_type |= XFS_DQTYPE_BIGTIME;
+
/*
* Reservation counters are defined as reservation plus current usage
* to avoid having to add every time.
@@ -1165,6 +1182,15 @@ xfs_qm_dqflush_check(
!dqp->q_rtb.timer)
return __this_address;

+ /*
+ * Except for the root dquot (whose timer values are the default grace
+ * period expirations) we should never write non-bigtime quota timers
+ * to a bigtime fs.
+ */
+ if (dqp->q_id != 0 && xfs_sb_version_hasbigtime(&dqp->q_mount->m_sb) &&
+ !(dqp->q_type & XFS_DQTYPE_BIGTIME))
+ return __this_address;
+
return NULL;
}

diff --git a/fs/xfs/xfs_dquot.h b/fs/xfs/xfs_dquot.h
index 0ba4d91c3a11..74ca87e02a14 100644
--- a/fs/xfs/xfs_dquot.h
+++ b/fs/xfs/xfs_dquot.h
@@ -238,6 +238,7 @@ int xfs_qm_dqiterate(struct xfs_mount *mp, xfs_dqtype_t type,
xfs_qm_dqiterate_fn iter_fn, void *priv);

void xfs_dquot_set_grace_period(time64_t *timer, time64_t limit);
-void xfs_dquot_set_timeout(time64_t *timer, time64_t limit);
+void xfs_dquot_set_timeout(struct xfs_mount *mp, time64_t *timer,
+ time64_t limit);

#endif /* __XFS_DQUOT_H__ */
diff --git a/fs/xfs/xfs_ondisk.h b/fs/xfs/xfs_ondisk.h
index 80129b2dc392..734a0fe7dd73 100644
--- a/fs/xfs/xfs_ondisk.h
+++ b/fs/xfs/xfs_ondisk.h
@@ -32,6 +32,13 @@ xfs_check_limits(void)
XFS_CHECK_VALUE(XFS_DQ_TIMEOUT_MAX, 4294967295LL);
XFS_CHECK_VALUE(XFS_DQ_GRACE_MIN, 0LL);
XFS_CHECK_VALUE(XFS_DQ_GRACE_MAX, 4294967295LL);
+ XFS_CHECK_VALUE(XFS_DQ_BIGTIMEOUT_MIN, 1LL);
+ XFS_CHECK_VALUE(XFS_DQ_BIGTIMEOUT_MAX, 16299260425LL);
+
+ BUILD_BUG_ON_MSG((XFS_DQ_TIMEOUT_MAX << XFS_DQ_BIGTIME_SHIFT) <
+ XFS_DQ_BIGTIMEOUT_MAX,
+ "XFS: quota timeout field is not large enough to fit "
+ "XFS_DQ_BIGTIMEOUT_MAX");
}

static inline void __init
diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c
index be67570badf8..a5136e40e118 100644
--- a/fs/xfs/xfs_qm.c
+++ b/fs/xfs/xfs_qm.c
@@ -879,6 +879,8 @@ xfs_qm_reset_dqcounts(
ddq->d_bwarns = 0;
ddq->d_iwarns = 0;
ddq->d_rtbwarns = 0;
+ if (xfs_sb_version_hasbigtime(&mp->m_sb))
+ ddq->d_type |= XFS_DQTYPE_BIGTIME;
}

if (xfs_sb_version_hascrc(&mp->m_sb)) {
diff --git a/fs/xfs/xfs_qm_syscalls.c b/fs/xfs/xfs_qm_syscalls.c
index 95b0c25b9969..4d9c245d65c2 100644
--- a/fs/xfs/xfs_qm_syscalls.c
+++ b/fs/xfs/xfs_qm_syscalls.c
@@ -479,6 +479,7 @@ xfs_setqlim_warns(

static inline void
xfs_setqlim_timer(
+ struct xfs_dquot *dqp,
struct xfs_dquot_res *res,
struct xfs_quota_limits *qlim,
s64 timer)
@@ -489,7 +490,7 @@ xfs_setqlim_timer(
qlim->time = res->timer;
} else {
/* Set the grace period expiration on a quota. */
- xfs_dquot_set_timeout(&res->timer, timer);
+ xfs_dquot_set_timeout(dqp->q_mount, &res->timer, timer);
}
}

@@ -579,7 +580,7 @@ xfs_qm_scall_setqlim(
if (newlim->d_fieldmask & QC_SPC_WARNS)
xfs_setqlim_warns(res, qlim, newlim->d_spc_warns);
if (newlim->d_fieldmask & QC_SPC_TIMER)
- xfs_setqlim_timer(res, qlim, newlim->d_spc_timer);
+ xfs_setqlim_timer(dqp, res, qlim, newlim->d_spc_timer);

/* Blocks on the realtime device. */
hard = (newlim->d_fieldmask & QC_RT_SPC_HARD) ?
@@ -595,7 +596,7 @@ xfs_qm_scall_setqlim(
if (newlim->d_fieldmask & QC_RT_SPC_WARNS)
xfs_setqlim_warns(res, qlim, newlim->d_rt_spc_warns);
if (newlim->d_fieldmask & QC_RT_SPC_TIMER)
- xfs_setqlim_timer(res, qlim, newlim->d_rt_spc_timer);
+ xfs_setqlim_timer(dqp, res, qlim, newlim->d_rt_spc_timer);

/* Inodes */
hard = (newlim->d_fieldmask & QC_INO_HARD) ?
@@ -611,7 +612,7 @@ xfs_qm_scall_setqlim(
if (newlim->d_fieldmask & QC_INO_WARNS)
xfs_setqlim_warns(res, qlim, newlim->d_ino_warns);
if (newlim->d_fieldmask & QC_INO_TIMER)
- xfs_setqlim_timer(res, qlim, newlim->d_ino_timer);
+ xfs_setqlim_timer(dqp, res, qlim, newlim->d_ino_timer);

if (id != 0) {
/*