]>
Commit | Line | Data |
---|---|---|
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. */ | |
14 | static bool noquota; | |
15 | ||
16 | void 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 | */ | |
25 | static 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 | */ | |
31 | uint16_t | |
32 | quotacheck_results(void) | |
33 | { | |
34 | return chkd_flags; | |
35 | } | |
36 | ||
0a8d74d6 DW |
37 | /* Global incore dquot tree */ |
38 | struct 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 | ||
51 | static struct qc_dquots *user_dquots; | |
52 | static struct qc_dquots *group_dquots; | |
53 | static 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 | ||
58 | struct 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 | ||
69 | static const char * | |
70 | qflags_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 | ||
84 | static uint64_t | |
85 | qc_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 | ||
94 | static uint64_t | |
95 | qc_avl_end( | |
96 | struct avl64node *node) | |
97 | { | |
98 | return qc_avl_start(node) + 1; | |
99 | } | |
100 | ||
101 | static 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. */ | |
107 | static struct qc_rec * | |
108 | qc_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. */ | |
135 | static void | |
136 | qc_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. */ | |
158 | static xfs_filblks_t | |
159 | qc_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. */ | |
184 | void | |
185 | quotacheck_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. */ |
236 | static bool | |
237 | qc_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. */ |
271 | static void | |
272 | qc_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 = ∅ | |
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. */ | |
346 | static int | |
347 | qc_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. */ | |
395 | void | |
396 | quotacheck_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 | } | |
478 | err: | |
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 | */ | |
487 | static inline bool | |
488 | qc_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. */ | |
526 | static struct qc_dquots * | |
527 | qc_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. */ | |
543 | int | |
544 | quotacheck_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; | |
578 | err: | |
579 | chkd_flags = 0; | |
580 | quotacheck_teardown(); | |
581 | return ENOMEM; | |
582 | } | |
583 | ||
584 | /* Purge all quotacheck records in a given cache. */ | |
585 | static void | |
586 | qc_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. */ | |
606 | void | |
607 | quotacheck_teardown(void) | |
608 | { | |
609 | qc_purge(&user_dquots); | |
610 | qc_purge(&group_dquots); | |
611 | qc_purge(&proj_dquots); | |
612 | } |