]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blame - repair/quotacheck.c
xfs_repair: don't log inode problems without printing resolution
[thirdparty/xfsprogs-dev.git] / repair / quotacheck.c
CommitLineData
0a8d74d6
DW
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) 2020 Oracle. All Rights Reserved.
4 * Author: Darrick J. Wong <darrick.wong@oracle.com>
5 */
6#include <libxfs.h>
7#include "globals.h"
8#include "versions.h"
9#include "err_protos.h"
10#include "libfrog/avl64.h"
11#include "quotacheck.h"
12
13/* Allow the xfs_repair caller to skip quotacheck entirely. */
14static bool noquota;
15
16void quotacheck_skip(void)
17{
18 noquota = true;
19}
20
21/*
22 * XFS_*QUOTA_CHKD flags for all the quota types that we verified. This field
23 * will be cleared if we encounter any problems (runtime errors, mismatches).
24 */
25static uint16_t chkd_flags;
26
91a52fbc
DW
27/*
28 * Return CHKD flags for the quota types that we checked. If we encountered
29 * any errors at all, return zero.
30 */
31uint16_t
32quotacheck_results(void)
33{
34 return chkd_flags;
35}
36
0a8d74d6
DW
37/* Global incore dquot tree */
38struct qc_dquots {
39 pthread_mutex_t lock;
40 struct avl64tree_desc tree;
41
8e4128a7 42 /* One of XFS_DQTYPE_USER/PROJ/GROUP */
4a4b0690 43 xfs_dqtype_t type;
0a8d74d6
DW
44};
45
46#define qc_dquots_foreach(dquots, pos, n) \
47 for (pos = (dquots)->tree.avl_firstino, n = pos ? pos->avl_nextino : NULL; \
48 pos != NULL; \
49 pos = n, n = pos ? pos->avl_nextino : NULL)
50
51static struct qc_dquots *user_dquots;
52static struct qc_dquots *group_dquots;
53static struct qc_dquots *proj_dquots;
54
55/* This record was found in the on-disk dquot information. */
56#define QC_REC_ONDISK (1U << 31)
57
58struct qc_rec {
59 struct avl64node node;
60 pthread_mutex_t lock;
61
62 xfs_dqid_t id;
63 uint32_t flags;
64 uint64_t bcount;
65 uint64_t rtbcount;
66 uint64_t icount;
67};
68
69static const char *
70qflags_typestr(
4a4b0690 71 xfs_dqtype_t type)
0a8d74d6 72{
8e4128a7 73 if (type & XFS_DQTYPE_USER)
0a8d74d6 74 return _("user quota");
8e4128a7 75 else if (type & XFS_DQTYPE_GROUP)
0a8d74d6 76 return _("group quota");
8e4128a7 77 else if (type & XFS_DQTYPE_PROJ)
0a8d74d6
DW
78 return _("project quota");
79 return NULL;
80}
81
82/* Operations for the avl64 tree. */
83
84static uint64_t
85qc_avl_start(
86 struct avl64node *node)
87{
88 struct qc_rec *qrec;
89
90 qrec = container_of(node, struct qc_rec, node);
91 return qrec->id;
92}
93
94static uint64_t
95qc_avl_end(
96 struct avl64node *node)
97{
98 return qc_avl_start(node) + 1;
99}
100
101static struct avl64ops qc_cache_ops = {
102 .avl_start = qc_avl_start,
103 .avl_end = qc_avl_end,
104};
105
106/* Find a qc_rec in the incore cache, or allocate one if need be. */
107static struct qc_rec *
108qc_rec_get(
109 struct qc_dquots *dquots,
110 xfs_dqid_t id,
111 bool can_alloc)
112{
113 struct qc_rec *qrec;
114 struct avl64node *node;
115
116 pthread_mutex_lock(&dquots->lock);
117 node = avl64_find(&dquots->tree, id);
118 if (!node && can_alloc) {
119 qrec = calloc(sizeof(struct qc_rec), 1);
120 if (qrec) {
121 qrec->id = id;
122 node = avl64_insert(&dquots->tree, &qrec->node);
123 if (!node)
124 free(qrec);
125 else
126 pthread_mutex_init(&qrec->lock, NULL);
127 }
128 }
129 pthread_mutex_unlock(&dquots->lock);
130
131 return node ? container_of(node, struct qc_rec, node) : NULL;
132}
133
134/* Bump up an incore dquot's counters. */
135static void
136qc_adjust(
137 struct qc_dquots *dquots,
138 xfs_dqid_t id,
139 uint64_t bcount,
140 uint64_t rtbcount)
141{
142 struct qc_rec *qrec = qc_rec_get(dquots, id, true);
143
144 if (!qrec) {
145 do_warn(_("Ran out of memory while running quotacheck!\n"));
146 chkd_flags = 0;
147 return;
148 }
149
150 pthread_mutex_lock(&qrec->lock);
151 qrec->bcount += bcount;
152 qrec->rtbcount += rtbcount;
153 qrec->icount++;
154 pthread_mutex_unlock(&qrec->lock);
155}
156
157/* Count the realtime blocks allocated to a file. */
158static xfs_filblks_t
159qc_count_rtblocks(
160 struct xfs_inode *ip)
161{
162 struct xfs_iext_cursor icur;
163 struct xfs_bmbt_irec got;
164 xfs_filblks_t count = 0;
722e81c1 165 struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK);
0a8d74d6
DW
166 int error;
167
30de9f1c
CH
168 error = -libxfs_iread_extents(NULL, ip, XFS_DATA_FORK);
169 if (error) {
170 do_warn(
0a8d74d6 171_("could not read ino %"PRIu64" extents, err=%d\n"),
30de9f1c
CH
172 ip->i_ino, error);
173 chkd_flags = 0;
174 return 0;
0a8d74d6
DW
175 }
176
177 for_each_xfs_iext(ifp, &icur, &got)
178 if (!isnullstartblock(got.br_startblock))
179 count += got.br_blockcount;
180 return count;
181}
182
183/* Add this inode's information to the quota counts. */
184void
185quotacheck_adjust(
186 struct xfs_mount *mp,
187 xfs_ino_t ino)
188{
189 struct xfs_inode *ip;
190 uint64_t blocks;
191 uint64_t rtblks = 0;
192 int error;
193
194 /*
195 * If the fs doesn't have any quota files to check against, skip this
196 * step.
197 */
198 if (!user_dquots && !group_dquots && !proj_dquots)
199 return;
200
201 /* Skip if a previous quotacheck adjustment failed. */
202 if (chkd_flags == 0)
203 return;
204
205 /* Quota files are not included in quota counts. */
206 if (ino == mp->m_sb.sb_uquotino ||
207 ino == mp->m_sb.sb_gquotino ||
208 ino == mp->m_sb.sb_pquotino)
209 return;
210
1fecabf9 211 error = -libxfs_iget(mp, NULL, ino, 0, &ip);
0a8d74d6
DW
212 if (error) {
213 do_warn(
214 _("could not open file %"PRIu64" for quotacheck, err=%d\n"),
215 ino, error);
216 chkd_flags = 0;
217 return;
218 }
219
220 /* Count the file's blocks. */
221 if (XFS_IS_REALTIME_INODE(ip))
222 rtblks = qc_count_rtblocks(ip);
aa00f286 223 blocks = ip->i_nblocks - rtblks;
0a8d74d6
DW
224
225 if (user_dquots)
226 qc_adjust(user_dquots, i_uid_read(VFS_I(ip)), blocks, rtblks);
227 if (group_dquots)
228 qc_adjust(group_dquots, i_gid_read(VFS_I(ip)), blocks, rtblks);
229 if (proj_dquots)
0ca7fa97 230 qc_adjust(proj_dquots, ip->i_projid, blocks, rtblks);
0a8d74d6
DW
231
232 libxfs_irele(ip);
233}
234
604bd75c
DW
235/* Check the ondisk dquot's id and type match what the incore dquot expects. */
236static bool
237qc_dquot_check_type(
238 struct xfs_mount *mp,
239 xfs_dqtype_t type,
240 xfs_dqid_t id,
241 struct xfs_disk_dquot *ddq)
242{
243 uint8_t ddq_type;
244
245 ddq_type = ddq->d_type & XFS_DQTYPE_REC_MASK;
246
247 if (be32_to_cpu(ddq->d_id) != id)
248 return false;
249
250 /*
251 * V5 filesystems always expect an exact type match. V4 filesystems
252 * expect an exact match for user dquots and for non-root group and
253 * project dquots.
254 */
2660e653 255 if (xfs_has_crc(mp) || type == XFS_DQTYPE_USER || id)
604bd75c
DW
256 return ddq_type == type;
257
258 /*
259 * V4 filesystems support either group or project quotas, but not both
260 * at the same time. The non-user quota file can be switched between
261 * group and project quota uses depending on the mount options, which
262 * means that we can encounter the other type when we try to load quota
263 * defaults. Quotacheck will soon reset the the entire quota file
264 * (including the root dquot) anyway, but don't log scary corruption
265 * reports to dmesg.
266 */
267 return ddq_type == XFS_DQTYPE_GROUP || ddq_type == XFS_DQTYPE_PROJ;
268}
269
0a8d74d6
DW
270/* Compare this on-disk dquot against whatever we observed. */
271static void
272qc_check_dquot(
37c7dda1 273 struct xfs_mount *mp,
0a8d74d6 274 struct xfs_disk_dquot *ddq,
604bd75c
DW
275 struct qc_dquots *dquots,
276 xfs_dqid_t dqid)
0a8d74d6
DW
277{
278 struct qc_rec *qrec;
279 struct qc_rec empty = {
280 .bcount = 0,
281 .rtbcount = 0,
282 .icount = 0,
283 };
284 xfs_dqid_t id = be32_to_cpu(ddq->d_id);
285
286 qrec = qc_rec_get(dquots, id, false);
287 if (!qrec)
288 qrec = &empty;
289
604bd75c
DW
290 if (!qc_dquot_check_type(mp, dquots->type, dqid, ddq)) {
291 const char *dqtypestr;
292
293 dqtypestr = qflags_typestr(ddq->d_type & XFS_DQTYPE_REC_MASK);
294 if (dqtypestr)
295 do_warn(_("%s id %u saw type %s id %u\n"),
296 qflags_typestr(dquots->type), dqid,
297 dqtypestr, be32_to_cpu(ddq->d_id));
298 else
299 do_warn(_("%s id %u saw type %x id %u\n"),
300 qflags_typestr(dquots->type), dqid,
301 ddq->d_type & XFS_DQTYPE_REC_MASK,
302 be32_to_cpu(ddq->d_id));
303 chkd_flags = 0;
304 }
305
0a8d74d6
DW
306 if (be64_to_cpu(ddq->d_bcount) != qrec->bcount) {
307 do_warn(_("%s id %u has bcount %llu, expected %"PRIu64"\n"),
308 qflags_typestr(dquots->type), id,
10eea710
DW
309 (unsigned long long)be64_to_cpu(ddq->d_bcount),
310 qrec->bcount);
0a8d74d6
DW
311 chkd_flags = 0;
312 }
313
314 if (be64_to_cpu(ddq->d_rtbcount) != qrec->rtbcount) {
315 do_warn(_("%s id %u has rtbcount %llu, expected %"PRIu64"\n"),
316 qflags_typestr(dquots->type), id,
10eea710
DW
317 (unsigned long long)be64_to_cpu(ddq->d_rtbcount),
318 qrec->rtbcount);
0a8d74d6
DW
319 chkd_flags = 0;
320 }
321
322 if (be64_to_cpu(ddq->d_icount) != qrec->icount) {
323 do_warn(_("%s id %u has icount %llu, expected %"PRIu64"\n"),
324 qflags_typestr(dquots->type), id,
10eea710
DW
325 (unsigned long long)be64_to_cpu(ddq->d_icount),
326 qrec->icount);
0a8d74d6
DW
327 chkd_flags = 0;
328 }
329
37c7dda1 330 if ((ddq->d_type & XFS_DQTYPE_BIGTIME) &&
2660e653 331 !xfs_has_bigtime(mp)) {
37c7dda1
DW
332 do_warn(
333 _("%s id %u is marked bigtime but file system does not support large timestamps\n"),
334 qflags_typestr(dquots->type), id);
335 chkd_flags = 0;
336 }
337
0a8d74d6
DW
338 /*
339 * Mark that we found the record on disk. Skip locking here because
340 * we're checking the dquots serially.
341 */
342 qrec->flags |= QC_REC_ONDISK;
343}
344
345/* Walk every dquot in every block in this quota inode extent and compare. */
346static int
347qc_walk_dquot_extent(
348 struct xfs_inode *ip,
349 struct xfs_bmbt_irec *map,
350 struct qc_dquots *dquots)
351{
352 struct xfs_mount *mp = ip->i_mount;
353 struct xfs_buf *bp;
354 struct xfs_dqblk *dqb;
355 xfs_filblks_t dqchunklen;
356 xfs_filblks_t bno;
357 unsigned int dqperchunk;
358 int error = 0;
359
360 dqchunklen = XFS_FSB_TO_BB(mp, XFS_DQUOT_CLUSTER_SIZE_FSB);
361 dqperchunk = libxfs_calc_dquots_per_chunk(dqchunklen);
362
363 for (bno = 0;
364 bno < map->br_blockcount;
365 bno += XFS_DQUOT_CLUSTER_SIZE_FSB) {
366 unsigned int dqnr;
367 uint64_t dqid;
368
369 error = -libxfs_buf_read(mp->m_dev,
370 XFS_FSB_TO_DADDR(mp, map->br_startblock + bno),
371 dqchunklen, 0, &bp, &xfs_dquot_buf_ops);
372 if (error) {
373 do_warn(
374_("cannot read %s inode %"PRIu64", block %"PRIu64", disk block %"PRIu64", err=%d\n"),
375 qflags_typestr(dquots->type), ip->i_ino,
376 map->br_startoff + bno,
377 map->br_startblock + bno, error);
378 chkd_flags = 0;
379 return error;
380 }
381
382 dqb = bp->b_addr;
604bd75c 383 dqid = (map->br_startoff + bno) * dqperchunk;
0a8d74d6
DW
384 for (dqnr = 0;
385 dqnr < dqperchunk && dqid <= UINT_MAX;
386 dqnr++, dqb++, dqid++)
604bd75c 387 qc_check_dquot(mp, &dqb->dd_diskdq, dquots, dqid);
0a8d74d6
DW
388 libxfs_buf_relse(bp);
389 }
390
391 return error;
392}
393
394/* Check the incore quota counts with what's on disk. */
395void
396quotacheck_verify(
397 struct xfs_mount *mp,
4a4b0690 398 xfs_dqtype_t type)
0a8d74d6
DW
399{
400 struct xfs_bmbt_irec map;
401 struct xfs_iext_cursor icur;
402 struct xfs_inode *ip;
403 struct xfs_ifork *ifp;
404 struct qc_dquots *dquots = NULL;
405 struct avl64node *node, *n;
406 xfs_ino_t ino = NULLFSINO;
407 int error;
408
409 switch (type) {
8e4128a7 410 case XFS_DQTYPE_USER:
0a8d74d6
DW
411 ino = mp->m_sb.sb_uquotino;
412 dquots = user_dquots;
413 break;
8e4128a7 414 case XFS_DQTYPE_GROUP:
0a8d74d6
DW
415 ino = mp->m_sb.sb_gquotino;
416 dquots = group_dquots;
417 break;
8e4128a7 418 case XFS_DQTYPE_PROJ:
0a8d74d6
DW
419 ino = mp->m_sb.sb_pquotino;
420 dquots = proj_dquots;
421 break;
422 }
423
424 /*
425 * If we decided not to build incore records or there were errors in
426 * collecting them that caused us to clear chkd_flags, bail out early.
427 * No sense in complaining more about garbage.
428 */
429 if (!dquots || !chkd_flags)
430 return;
431
1fecabf9 432 error = -libxfs_iget(mp, NULL, ino, 0, &ip);
0a8d74d6
DW
433 if (error) {
434 do_warn(
435 _("could not open %s inode %"PRIu64" for quotacheck, err=%d\n"),
436 qflags_typestr(type), ino, error);
437 chkd_flags = 0;
438 return;
439 }
440
722e81c1 441 ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK);
30de9f1c
CH
442 error = -libxfs_iread_extents(NULL, ip, XFS_DATA_FORK);
443 if (error) {
444 do_warn(
0a8d74d6 445 _("could not read %s inode %"PRIu64" extents, err=%d\n"),
30de9f1c
CH
446 qflags_typestr(type), ip->i_ino, error);
447 chkd_flags = 0;
448 goto err;
0a8d74d6
DW
449 }
450
451 /* Walk each extent of the quota inode and compare counters. */
452 for_each_xfs_iext(ifp, &icur, &map) {
453 if (map.br_startblock != HOLESTARTBLOCK) {
454 error = qc_walk_dquot_extent(ip, &map, dquots);
455 if (error)
456 goto err;
457 }
458 }
459
460 /*
461 * We constructed incore dquots to account for every file we saw on
462 * disk, and then walked all on-disk dquots to compare. Complain about
463 * incore dquots that weren't touched during the comparison, because
464 * that means something is missing from the dquot file.
465 */
466 qc_dquots_foreach(dquots, node, n) {
467 struct qc_rec *qrec;
468
469 qrec = container_of(node, struct qc_rec, node);
470 if (!(qrec->flags & QC_REC_ONDISK)) {
471 do_warn(
472_("%s record for id %u not found on disk (bcount %"PRIu64" rtbcount %"PRIu64" icount %"PRIu64")\n"),
473 qflags_typestr(type), qrec->id,
474 qrec->bcount, qrec->rtbcount, qrec->icount);
475 chkd_flags = 0;
476 }
477 }
478err:
479 libxfs_irele(ip);
480}
481
482/*
483 * Decide if we want to run quotacheck on a particular quota type. Returns
484 * true only if the inode isn't lost, the fs says quotacheck ran, and the
485 * ondisk pointer to the quota inode isn't "unset" (e.g. NULL or zero).
486 */
487static inline bool
488qc_has_quotafile(
489 struct xfs_mount *mp,
4a4b0690 490 xfs_dqtype_t type)
0a8d74d6
DW
491{
492 bool lost;
493 xfs_ino_t ino;
494 unsigned int qflag;
495
496 switch (type) {
8e4128a7 497 case XFS_DQTYPE_USER:
0a8d74d6
DW
498 lost = lost_uquotino;
499 ino = mp->m_sb.sb_uquotino;
500 qflag = XFS_UQUOTA_CHKD;
501 break;
8e4128a7 502 case XFS_DQTYPE_GROUP:
0a8d74d6
DW
503 lost = lost_gquotino;
504 ino = mp->m_sb.sb_gquotino;
505 qflag = XFS_GQUOTA_CHKD;
506 break;
8e4128a7 507 case XFS_DQTYPE_PROJ:
0a8d74d6
DW
508 lost = lost_pquotino;
509 ino = mp->m_sb.sb_pquotino;
510 qflag = XFS_PQUOTA_CHKD;
511 break;
512 default:
513 return false;
514 }
515
516 if (lost)
517 return false;
518 if (!(mp->m_sb.sb_qflags & qflag))
519 return false;
520 if (ino == NULLFSINO || ino == 0)
521 return false;
522 return true;
523}
524
525/* Initialize an incore dquot tree. */
526static struct qc_dquots *
527qc_dquots_init(
4a4b0690 528 xfs_dqtype_t type)
0a8d74d6
DW
529{
530 struct qc_dquots *dquots;
531
532 dquots = calloc(1, sizeof(struct qc_dquots));
533 if (!dquots)
534 return NULL;
535
536 dquots->type = type;
537 pthread_mutex_init(&dquots->lock, NULL);
538 avl64_init_tree(&dquots->tree, &qc_cache_ops);
539 return dquots;
540}
541
542/* Set up incore context for quota checks. */
543int
544quotacheck_setup(
545 struct xfs_mount *mp)
546{
547 chkd_flags = 0;
548
549 /*
550 * If the superblock said quotas are disabled or was missing pointers
551 * to any quota inodes, don't bother checking.
552 */
553 if (!fs_quotas || lost_quotas || noquota)
554 return 0;
555
8e4128a7
DW
556 if (qc_has_quotafile(mp, XFS_DQTYPE_USER)) {
557 user_dquots = qc_dquots_init(XFS_DQTYPE_USER);
0a8d74d6
DW
558 if (!user_dquots)
559 goto err;
560 chkd_flags |= XFS_UQUOTA_CHKD;
561 }
562
8e4128a7
DW
563 if (qc_has_quotafile(mp, XFS_DQTYPE_GROUP)) {
564 group_dquots = qc_dquots_init(XFS_DQTYPE_GROUP);
0a8d74d6
DW
565 if (!group_dquots)
566 goto err;
567 chkd_flags |= XFS_GQUOTA_CHKD;
568 }
569
8e4128a7
DW
570 if (qc_has_quotafile(mp, XFS_DQTYPE_PROJ)) {
571 proj_dquots = qc_dquots_init(XFS_DQTYPE_PROJ);
0a8d74d6
DW
572 if (!proj_dquots)
573 goto err;
574 chkd_flags |= XFS_PQUOTA_CHKD;
575 }
576
577 return 0;
578err:
579 chkd_flags = 0;
580 quotacheck_teardown();
581 return ENOMEM;
582}
583
584/* Purge all quotacheck records in a given cache. */
585static void
586qc_purge(
587 struct qc_dquots **dquotsp)
588{
589 struct qc_dquots *dquots = *dquotsp;
590 struct qc_rec *qrec;
591 struct avl64node *node;
592 struct avl64node *n;
593
594 if (!dquots)
595 return;
596
597 qc_dquots_foreach(dquots, node, n) {
598 qrec = container_of(node, struct qc_rec, node);
599 free(qrec);
600 }
601 free(dquots);
602 *dquotsp = NULL;
603}
604
605/* Tear down all the incore context from quotacheck. */
606void
607quotacheck_teardown(void)
608{
609 qc_purge(&user_dquots);
610 qc_purge(&group_dquots);
611 qc_purge(&proj_dquots);
612}