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. */
43static int
44xqcheck_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
128out_unlock:
129 mutex_unlock(&xqc->lock);
130out_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. */
139STATIC int
140xqcheck_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. */
202static inline unsigned int
203xqcheck_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. */
218int
219xrep_quotacheck(
220 struct xfs_scrub *sc)
221{
222 struct xqcheck *xqc = sc->buf;
223 unsigned int qflags = xqcheck_chkd_flags(mp: 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}
262

source code of linux/fs/xfs/scrub/quotacheck_repair.c