Skip to content

Commit 96ed2ae

Browse files
author
Darrick J. Wong
committed
xfs: repair dquots based on live quotacheck results
Use the shadow quota counters that live quotacheck creates to reset the incore dquot counters. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de>
1 parent 7038c6e commit 96ed2ae

8 files changed

Lines changed: 284 additions & 6 deletions

File tree

fs/xfs/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ xfs-$(CONFIG_XFS_RT) += $(addprefix scrub/, \
204204

205205
xfs-$(CONFIG_XFS_QUOTA) += $(addprefix scrub/, \
206206
quota_repair.o \
207+
quotacheck_repair.o \
207208
)
208209
endif
209210
endif

fs/xfs/scrub/quotacheck.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ xchk_setup_quotacheck(
102102
* set the INCOMPLETE flag even when a negative errno is returned. This care
103103
* must be taken with certain errno values (i.e. EFSBADCRC, EFSCORRUPTED,
104104
* ECANCELED) that are absorbed into a scrub state flag update by
105-
* xchk_*_process_error.
105+
* xchk_*_process_error. Scrub and repair share the same incore data
106+
* structures, so the INCOMPLETE flag is critical to prevent a repair based on
107+
* insufficient information.
106108
*
107109
* Because we are scanning a live filesystem, it's possible that another thread
108110
* will try to update the quota counters for an inode that we've already

fs/xfs/scrub/quotacheck.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ struct xqcheck_dquot {
3030
/* Already checked this dquot. */
3131
#define XQCHECK_DQUOT_COMPARE_SCANNED (1U << 1)
3232

33+
/* Already repaired this dquot. */
34+
#define XQCHECK_DQUOT_REPAIR_SCANNED (1U << 2)
35+
3336
/* Live quotacheck control structure. */
3437
struct xqcheck {
3538
struct xfs_scrub *sc;

fs/xfs/scrub/quotacheck_repair.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright (c) 2020-2024 Oracle. All Rights Reserved.
4+
* Author: Darrick J. Wong <djwong@kernel.org>
5+
*/
6+
#include "xfs.h"
7+
#include "xfs_fs.h"
8+
#include "xfs_shared.h"
9+
#include "xfs_format.h"
10+
#include "xfs_trans_resv.h"
11+
#include "xfs_mount.h"
12+
#include "xfs_log_format.h"
13+
#include "xfs_trans.h"
14+
#include "xfs_inode.h"
15+
#include "xfs_quota.h"
16+
#include "xfs_qm.h"
17+
#include "xfs_icache.h"
18+
#include "xfs_bmap_util.h"
19+
#include "xfs_iwalk.h"
20+
#include "xfs_ialloc.h"
21+
#include "xfs_sb.h"
22+
#include "scrub/scrub.h"
23+
#include "scrub/common.h"
24+
#include "scrub/repair.h"
25+
#include "scrub/xfile.h"
26+
#include "scrub/xfarray.h"
27+
#include "scrub/iscan.h"
28+
#include "scrub/quota.h"
29+
#include "scrub/quotacheck.h"
30+
#include "scrub/trace.h"
31+
32+
/*
33+
* Live Quotacheck Repair
34+
* ======================
35+
*
36+
* Use the live quota counter information that we collected to replace the
37+
* counter values in the incore dquots. A scrub->repair cycle should have left
38+
* the live data and hooks active, so this is safe so long as we make sure the
39+
* dquot is locked.
40+
*/
41+
42+
/* Commit new counters to a dquot. */
43+
static int
44+
xqcheck_commit_dquot(
45+
struct xqcheck *xqc,
46+
xfs_dqtype_t dqtype,
47+
struct xfs_dquot *dq)
48+
{
49+
struct xqcheck_dquot xcdq;
50+
struct xfarray *counts = xqcheck_counters_for(xqc, dqtype);
51+
int64_t delta;
52+
bool dirty = false;
53+
int error = 0;
54+
55+
/* Unlock the dquot just long enough to allocate a transaction. */
56+
xfs_dqunlock(dq);
57+
error = xchk_trans_alloc(xqc->sc, 0);
58+
xfs_dqlock(dq);
59+
if (error)
60+
return error;
61+
62+
xfs_trans_dqjoin(xqc->sc->tp, dq);
63+
64+
if (xchk_iscan_aborted(&xqc->iscan)) {
65+
error = -ECANCELED;
66+
goto out_cancel;
67+
}
68+
69+
mutex_lock(&xqc->lock);
70+
error = xfarray_load_sparse(counts, dq->q_id, &xcdq);
71+
if (error)
72+
goto out_unlock;
73+
74+
/* Adjust counters as needed. */
75+
delta = (int64_t)xcdq.icount - dq->q_ino.count;
76+
if (delta) {
77+
dq->q_ino.reserved += delta;
78+
dq->q_ino.count += delta;
79+
dirty = true;
80+
}
81+
82+
delta = (int64_t)xcdq.bcount - dq->q_blk.count;
83+
if (delta) {
84+
dq->q_blk.reserved += delta;
85+
dq->q_blk.count += delta;
86+
dirty = true;
87+
}
88+
89+
delta = (int64_t)xcdq.rtbcount - dq->q_rtb.count;
90+
if (delta) {
91+
dq->q_rtb.reserved += delta;
92+
dq->q_rtb.count += delta;
93+
dirty = true;
94+
}
95+
96+
xcdq.flags |= (XQCHECK_DQUOT_REPAIR_SCANNED | XQCHECK_DQUOT_WRITTEN);
97+
error = xfarray_store(counts, dq->q_id, &xcdq);
98+
if (error == -EFBIG) {
99+
/*
100+
* EFBIG means we tried to store data at too high a byte offset
101+
* in the sparse array. IOWs, we cannot complete the repair
102+
* and must cancel the whole operation. This should never
103+
* happen, but we need to catch it anyway.
104+
*/
105+
error = -ECANCELED;
106+
}
107+
mutex_unlock(&xqc->lock);
108+
if (error || !dirty)
109+
goto out_cancel;
110+
111+
trace_xrep_quotacheck_dquot(xqc->sc->mp, dq->q_type, dq->q_id);
112+
113+
/* Commit the dirty dquot to disk. */
114+
dq->q_flags |= XFS_DQFLAG_DIRTY;
115+
if (dq->q_id)
116+
xfs_qm_adjust_dqtimers(dq);
117+
xfs_trans_log_dquot(xqc->sc->tp, dq);
118+
119+
/*
120+
* Transaction commit unlocks the dquot, so we must re-lock it so that
121+
* the caller can put the reference (which apparently requires a locked
122+
* dquot).
123+
*/
124+
error = xrep_trans_commit(xqc->sc);
125+
xfs_dqlock(dq);
126+
return error;
127+
128+
out_unlock:
129+
mutex_unlock(&xqc->lock);
130+
out_cancel:
131+
xchk_trans_cancel(xqc->sc);
132+
133+
/* Re-lock the dquot so the caller can put the reference. */
134+
xfs_dqlock(dq);
135+
return error;
136+
}
137+
138+
/* Commit new quota counters for a particular quota type. */
139+
STATIC int
140+
xqcheck_commit_dqtype(
141+
struct xqcheck *xqc,
142+
unsigned int dqtype)
143+
{
144+
struct xchk_dqiter cursor = { };
145+
struct xqcheck_dquot xcdq;
146+
struct xfs_scrub *sc = xqc->sc;
147+
struct xfs_mount *mp = sc->mp;
148+
struct xfarray *counts = xqcheck_counters_for(xqc, dqtype);
149+
struct xfs_dquot *dq;
150+
xfarray_idx_t cur = XFARRAY_CURSOR_INIT;
151+
int error;
152+
153+
/*
154+
* Update the counters of every dquot that the quota file knows about.
155+
*/
156+
xchk_dqiter_init(&cursor, sc, dqtype);
157+
while ((error = xchk_dquot_iter(&cursor, &dq)) == 1) {
158+
error = xqcheck_commit_dquot(xqc, dqtype, dq);
159+
xfs_qm_dqput(dq);
160+
if (error)
161+
break;
162+
}
163+
if (error)
164+
return error;
165+
166+
/*
167+
* Make a second pass to deal with the dquots that we know about but
168+
* the quota file previously did not know about.
169+
*/
170+
mutex_lock(&xqc->lock);
171+
while ((error = xfarray_iter(counts, &cur, &xcdq)) == 1) {
172+
xfs_dqid_t id = cur - 1;
173+
174+
if (xcdq.flags & XQCHECK_DQUOT_REPAIR_SCANNED)
175+
continue;
176+
177+
mutex_unlock(&xqc->lock);
178+
179+
/*
180+
* Grab the dquot, allowing for dquot block allocation in a
181+
* separate transaction. We committed the scrub transaction
182+
* in a previous step, so we will not be creating nested
183+
* transactions here.
184+
*/
185+
error = xfs_qm_dqget(mp, id, dqtype, true, &dq);
186+
if (error)
187+
return error;
188+
189+
error = xqcheck_commit_dquot(xqc, dqtype, dq);
190+
xfs_qm_dqput(dq);
191+
if (error)
192+
return error;
193+
194+
mutex_lock(&xqc->lock);
195+
}
196+
mutex_unlock(&xqc->lock);
197+
198+
return error;
199+
}
200+
201+
/* Figure out quota CHKD flags for the running quota types. */
202+
static inline unsigned int
203+
xqcheck_chkd_flags(
204+
struct xfs_mount *mp)
205+
{
206+
unsigned int ret = 0;
207+
208+
if (XFS_IS_UQUOTA_ON(mp))
209+
ret |= XFS_UQUOTA_CHKD;
210+
if (XFS_IS_GQUOTA_ON(mp))
211+
ret |= XFS_GQUOTA_CHKD;
212+
if (XFS_IS_PQUOTA_ON(mp))
213+
ret |= XFS_PQUOTA_CHKD;
214+
return ret;
215+
}
216+
217+
/* Commit the new dquot counters. */
218+
int
219+
xrep_quotacheck(
220+
struct xfs_scrub *sc)
221+
{
222+
struct xqcheck *xqc = sc->buf;
223+
unsigned int qflags = xqcheck_chkd_flags(sc->mp);
224+
int error;
225+
226+
/*
227+
* Clear the CHKD flag for the running quota types and commit the scrub
228+
* transaction so that we can allocate new quota block mappings if we
229+
* have to. If we crash after this point, the sb still has the CHKD
230+
* flags cleared, so mount quotacheck will fix all of this up.
231+
*/
232+
xrep_update_qflags(sc, qflags, 0);
233+
error = xrep_trans_commit(sc);
234+
if (error)
235+
return error;
236+
237+
/* Commit the new counters to the dquots. */
238+
if (xqc->ucounts) {
239+
error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_USER);
240+
if (error)
241+
return error;
242+
}
243+
if (xqc->gcounts) {
244+
error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_GROUP);
245+
if (error)
246+
return error;
247+
}
248+
if (xqc->pcounts) {
249+
error = xqcheck_commit_dqtype(xqc, XFS_DQTYPE_PROJ);
250+
if (error)
251+
return error;
252+
}
253+
254+
/* Set the CHKD flags now that we've fixed quota counts. */
255+
error = xchk_trans_alloc(sc, 0);
256+
if (error)
257+
return error;
258+
259+
xrep_update_qflags(sc, 0, qflags);
260+
return xrep_trans_commit(sc);
261+
}

fs/xfs/scrub/repair.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -688,21 +688,26 @@ xrep_find_ag_btree_roots(
688688

689689
#ifdef CONFIG_XFS_QUOTA
690690
/* Update some quota flags in the superblock. */
691-
static void
691+
void
692692
xrep_update_qflags(
693693
struct xfs_scrub *sc,
694-
unsigned int clear_flags)
694+
unsigned int clear_flags,
695+
unsigned int set_flags)
695696
{
696697
struct xfs_mount *mp = sc->mp;
697698
struct xfs_buf *bp;
698699

699700
mutex_lock(&mp->m_quotainfo->qi_quotaofflock);
700-
if ((mp->m_qflags & clear_flags) == 0)
701+
if ((mp->m_qflags & clear_flags) == 0 &&
702+
(mp->m_qflags & set_flags) == set_flags)
701703
goto no_update;
702704

703705
mp->m_qflags &= ~clear_flags;
706+
mp->m_qflags |= set_flags;
707+
704708
spin_lock(&mp->m_sb_lock);
705709
mp->m_sb.sb_qflags &= ~clear_flags;
710+
mp->m_sb.sb_qflags |= set_flags;
706711
spin_unlock(&mp->m_sb_lock);
707712

708713
/*
@@ -732,7 +737,7 @@ xrep_force_quotacheck(
732737
if (!(flag & sc->mp->m_qflags))
733738
return;
734739

735-
xrep_update_qflags(sc, flag);
740+
xrep_update_qflags(sc, flag, 0);
736741
}
737742

738743
/*

fs/xfs/scrub/repair.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ int xrep_find_ag_btree_roots(struct xfs_scrub *sc, struct xfs_buf *agf_bp,
7272
struct xrep_find_ag_btree *btree_info, struct xfs_buf *agfl_bp);
7373

7474
#ifdef CONFIG_XFS_QUOTA
75+
void xrep_update_qflags(struct xfs_scrub *sc, unsigned int clear_flags,
76+
unsigned int set_flags);
7577
void xrep_force_quotacheck(struct xfs_scrub *sc, xfs_dqtype_t type);
7678
int xrep_ino_dqattach(struct xfs_scrub *sc);
7779
#else
@@ -123,8 +125,10 @@ int xrep_rtbitmap(struct xfs_scrub *sc);
123125

124126
#ifdef CONFIG_XFS_QUOTA
125127
int xrep_quota(struct xfs_scrub *sc);
128+
int xrep_quotacheck(struct xfs_scrub *sc);
126129
#else
127130
# define xrep_quota xrep_notsupported
131+
# define xrep_quotacheck xrep_notsupported
128132
#endif /* CONFIG_XFS_QUOTA */
129133

130134
int xrep_reinit_pagf(struct xfs_scrub *sc);
@@ -191,6 +195,7 @@ xrep_setup_nothing(
191195
#define xrep_bmap_cow xrep_notsupported
192196
#define xrep_rtbitmap xrep_notsupported
193197
#define xrep_quota xrep_notsupported
198+
#define xrep_quotacheck xrep_notsupported
194199

195200
#endif /* CONFIG_XFS_ONLINE_REPAIR */
196201

fs/xfs/scrub/scrub.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = {
367367
.type = ST_FS,
368368
.setup = xchk_setup_quotacheck,
369369
.scrub = xchk_quotacheck,
370-
.repair = xrep_notsupported,
370+
.repair = xrep_quotacheck,
371371
},
372372
};
373373

fs/xfs/scrub/trace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,6 +2004,7 @@ DEFINE_EVENT(xrep_dquot_class, name, \
20042004
DEFINE_XREP_DQUOT_EVENT(xrep_dquot_item);
20052005
DEFINE_XREP_DQUOT_EVENT(xrep_disk_dquot);
20062006
DEFINE_XREP_DQUOT_EVENT(xrep_dquot_item_fill_bmap_hole);
2007+
DEFINE_XREP_DQUOT_EVENT(xrep_quotacheck_dquot);
20072008
#endif /* CONFIG_XFS_QUOTA */
20082009

20092010
#endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */

0 commit comments

Comments
 (0)