1/* $NetBSD: quota2.c,v 1.8 2023/07/04 20:40:53 riastradh Exp $ */ 2 3/*- 4 * Copyright (c) 2010 Manuel Bouyer 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include <sys/param.h> 30#include <sys/time.h> 31 32#include <ufs/ufs/dinode.h> 33#include <ufs/ffs/fs.h> 34#include <ufs/ffs/ffs_extern.h> 35#include <ufs/ufs/ufs_bswap.h> 36 37#include <err.h> 38#include <string.h> 39#include <stdlib.h> 40#include <ufs/ufs/quota2.h> 41 42#include "fsutil.h" 43#include "fsck.h" 44#include "extern.h" 45#include "exitvalues.h" 46 47static char **quotamap; 48 49void 50quota2_create_inode(struct fs *fs, int type) 51{ 52 ino_t ino; 53 struct bufarea *bp; 54 union dinode *dp; 55 56 ino = allocino(0, IFREG); 57 dp = ginode(ino); 58 DIP_SET(dp, nlink, iswap16(1)); 59 inodirty(); 60 61 if (readblk(dp, 0, &bp) != sblock->fs_bsize || 62 bp->b_errs != 0) { 63 freeino(ino); 64 return; 65 } 66 quota2_create_blk0(sblock->fs_bsize, bp->b_un.b_buf, 67 q2h_hash_shift, type, needswap); 68 dirty(bp); 69 bp->b_flags &= ~B_INUSE; 70 sblock->fs_quotafile[type] = ino; 71 sbdirty(); 72 return; 73} 74 75int 76quota2_alloc_quota(union dinode * dp, struct bufarea *hbp, 77 uid_t uid, uint64_t u_b, uint64_t u_i) 78{ 79 struct bufarea *bp; 80 struct quota2_header *q2h = (void *)hbp->b_un.b_buf; 81 struct quota2_entry *q2e; 82 uint64_t off; 83 uint64_t baseoff; 84 85 off = iswap64(q2h->q2h_free); 86 if (off == 0) { 87 baseoff = iswap64(DIP(dp, size)); 88 if ((bp = expandfile(dp)) == NULL) { 89 pfatal("SORRY, CAN'T EXPAND QUOTA INODE\n"); 90 markclean = 0; 91 return (0); 92 } 93 quota2_addfreeq2e(q2h, bp->b_un.b_buf, baseoff, 94 sblock->fs_bsize, needswap); 95 dirty(bp); 96 bp->b_flags &= ~B_INUSE; 97 off = iswap64(q2h->q2h_free); 98 if (off == 0) 99 errexit("INTERNAL ERROR: " 100 "addfreeq2e didn't fill free list\n"); 101 } 102 if (off < (uint64_t)sblock->fs_bsize) { 103 /* in the header block */ 104 bp = hbp; 105 } else { 106 if (readblk(dp, off, &bp) != sblock->fs_bsize || 107 bp->b_errs != 0) { 108 pwarn("CAN'T READ QUOTA BLOCK\n"); 109 return FSCK_EXIT_CHECK_FAILED; 110 } 111 } 112 q2e = (void *)((caddr_t)(bp->b_un.b_buf) + 113 (off % sblock->fs_bsize)); 114 /* remove from free list */ 115 q2h->q2h_free = q2e->q2e_next; 116 117 memcpy(q2e, &q2h->q2h_defentry, sizeof(*q2e)); 118 q2e->q2e_uid = iswap32(uid); 119 q2e->q2e_val[QL_BLOCK].q2v_cur = iswap64(u_b); 120 q2e->q2e_val[QL_FILE].q2v_cur = iswap64(u_i); 121 /* insert in hash list */ 122 q2e->q2e_next = q2h->q2h_entries[uid & q2h_hash_mask]; 123 q2h->q2h_entries[uid & q2h_hash_mask] = iswap64(off); 124 dirty(bp); 125 dirty(hbp); 126 127 if (bp != hbp) 128 bp->b_flags &= ~B_INUSE; 129 return 0; 130} 131 132/* walk a quota entry list, calling the callback for each entry */ 133static int quota2_walk_list(union dinode *, struct bufarea *, uint64_t *, 134 void *, int (*func)(uint64_t *, struct quota2_entry *, uint64_t, void *)); 135/* flags used by callbacks return */ 136#define Q2WL_ABORT 0x01 137#define Q2WL_DIRTY 0x02 138 139static int 140quota2_walk_list(union dinode *dp, struct bufarea *hbp, uint64_t *offp, void *a, 141 int (*func)(uint64_t *, struct quota2_entry *, uint64_t, void *)) 142{ 143 daddr_t off = iswap64(*offp); 144 struct bufarea *bp, *obp = hbp; 145 int ret; 146 struct quota2_entry *q2e; 147 148 while (off != 0) { 149 if (off < sblock->fs_bsize) { 150 /* in the header block */ 151 bp = hbp; 152 } else { 153 if (readblk(dp, off, &bp) != sblock->fs_bsize || 154 bp->b_errs != 0) { 155 pwarn("CAN'T READ QUOTA BLOCK"); 156 return FSCK_EXIT_CHECK_FAILED; 157 } 158 } 159 q2e = (void *)((caddr_t)(bp->b_un.b_buf) + 160 (off % sblock->fs_bsize)); 161 ret = (*func)(offp, q2e, off, a); 162 if (ret & Q2WL_DIRTY) 163 dirty(bp); 164 if (ret & Q2WL_ABORT) 165 return FSCK_EXIT_CHECK_FAILED; 166 if ((uint64_t)off != iswap64(*offp)) { 167 /* callback changed parent's pointer, redo */ 168 dirty(obp); 169 off = iswap64(*offp); 170 if (bp != hbp && bp != obp) 171 bp->b_flags &= ~B_INUSE; 172 } else { 173 /* parent if now current */ 174 if (obp != bp && obp != hbp) 175 obp->b_flags &= ~B_INUSE; 176 obp = bp; 177 offp = &(q2e->q2e_next); 178 off = iswap64(*offp); 179 } 180 } 181 if (obp != hbp) 182 obp->b_flags &= ~B_INUSE; 183 return 0; 184} 185 186static int quota2_list_check(uint64_t *, struct quota2_entry *, uint64_t, 187 void *); 188static int 189quota2_list_check(uint64_t *offp, struct quota2_entry *q2e, uint64_t off, 190 void *v) 191{ 192 int *hash = v; 193 const int quota2_hash_size = 1 << q2h_hash_shift; 194 const int quota2_full_header_size = sizeof(struct quota2_header) + 195 sizeof(uint64_t) * quota2_hash_size; 196 uint64_t blk = off / sblock->fs_bsize; 197 uint64_t boff = off % sblock->fs_bsize; 198 int qidx = off2qindex((blk == 0) ? quota2_full_header_size : 0, boff); 199 200 /* check that we're not already in a list */ 201 if (!isset(quotamap[blk], qidx)) { 202 pwarn("DUPLICATE QUOTA ENTRY"); 203 } else { 204 clrbit(quotamap[blk], qidx); 205 /* check that we're in the right hash entry */ 206 if (*hash < 0) 207 return 0; 208 if ((uint32_t)*hash == (iswap32(q2e->q2e_uid) & q2h_hash_mask)) 209 return 0; 210 211 pwarn("QUOTA uid %d IN WRONG HASH LIST %d", 212 iswap32(q2e->q2e_uid), *hash); 213 /* 214 * remove from list, but keep the bit so 215 * it'll be added back to the free list 216 */ 217 setbit(quotamap[blk], qidx); 218 } 219 220 if (preen) 221 printf(" (FIXED)\n"); 222 else if (!reply("FIX")) { 223 markclean = 0; 224 return 0; 225 } 226 /* remove this entry from the list */ 227 *offp = q2e->q2e_next; 228 q2e->q2e_next = 0; 229 return Q2WL_DIRTY; 230} 231 232int 233quota2_check_inode(int type) 234{ 235 const char *strtype = (type == USRQUOTA) ? "user" : "group"; 236 const char *capstrtype = (type == USRQUOTA) ? "USER" : "GROUP"; 237 238 struct bufarea *bp, *hbp; 239 union dinode *dp; 240 struct quota2_header *q2h; 241 struct quota2_entry *q2e; 242 int freei = 0; 243 int mode; 244 daddr_t off; 245 int nq2e, nq2map, i, j, ret; 246 uint64_t /* blocks, e_blocks, */ filesize; 247 248 const int quota2_hash_size = 1 << q2h_hash_shift; 249 const int quota2_full_header_size = sizeof(struct quota2_header) + 250 sizeof(q2h->q2h_entries[0]) * quota2_hash_size; 251 252 if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(type)) == 0) 253 return 0; 254 if (sblock->fs_quotafile[type] != 0) { 255 struct inostat *info; 256 257 info = inoinfo(sblock->fs_quotafile[type]); 258 switch(info->ino_state) { 259 case FSTATE: 260 break; 261 case DSTATE: 262 freei = 1; 263 /* FALLTHROUGH */ 264 case DFOUND: 265 pwarn("%s QUOTA INODE %" PRIu64 " IS A DIRECTORY", 266 capstrtype, sblock->fs_quotafile[type]); 267 goto clear; 268 case USTATE: 269 case DCLEAR: 270 case FCLEAR: 271 pwarn("UNALLOCATED %s QUOTA INODE %" PRIu64, 272 capstrtype, sblock->fs_quotafile[type]); 273 goto clear; 274 default: 275 pfatal("INTERNAL ERROR: wrong quota inode %" PRIu64 276 " type %d\n", sblock->fs_quotafile[type], 277 info->ino_state); 278 exit(FSCK_EXIT_CHECK_FAILED); 279 } 280 dp = ginode(sblock->fs_quotafile[type]); 281 mode = iswap16(DIP(dp, mode)) & IFMT; 282 switch(mode) { 283 case IFREG: 284 break; 285 default: 286 pwarn("WRONG TYPE %d for %s QUOTA INODE %" PRIu64, 287 mode, capstrtype, sblock->fs_quotafile[type]); 288 freei = 1; 289 goto clear; 290 } 291#if 0 292 blocks = is_ufs2 ? iswap64(dp->dp2.di_blocks) : 293 iswap32(dp->dp1.di_blocks); 294 filesize = iswap64(DIP(dp, size)); 295 e_blocks = btodb(filesize); 296 if (btodb(filesize) != blocks) { 297 pwarn("%s QUOTA INODE %" PRIu64 " HAS EMPTY BLOCKS", 298 capstrtype, sblock->fs_quotafile[type]); 299 freei = 1; 300 goto clear; 301 } 302#endif 303 if (readblk(dp, 0, &hbp) != sblock->fs_bsize || 304 hbp->b_errs != 0) { 305 freeino(sblock->fs_quotafile[type]); 306 sblock->fs_quotafile[type] = 0; 307 goto alloc; 308 } 309 q2h = (void *)hbp->b_un.b_buf; 310 if (q2h->q2h_magic_number != iswap32(Q2_HEAD_MAGIC) || 311 q2h->q2h_type != type || 312 q2h->q2h_hash_shift != q2h_hash_shift || 313 q2h->q2h_hash_size != iswap16(quota2_hash_size)) { 314 pwarn("CORRUPTED %s QUOTA INODE %" PRIu64, capstrtype, 315 sblock->fs_quotafile[type]); 316 freei = 1; 317 hbp->b_flags &= ~B_INUSE; 318clear: 319 if (preen) 320 printf(" (CLEARED)\n"); 321 else { 322 if (!reply("CLEAR")) { 323 markclean = 0; 324 return FSCK_EXIT_CHECK_FAILED; 325 } 326 } 327 if (freei) 328 freeino(sblock->fs_quotafile[type]); 329 sblock->fs_quotafile[type] = 0; 330 } 331 } 332alloc: 333 if (sblock->fs_quotafile[type] == 0) { 334 pwarn("NO %s QUOTA INODE", capstrtype); 335 if (preen) 336 printf(" (CREATED)\n"); 337 else { 338 if (!reply("CREATE")) { 339 markclean = 0; 340 return FSCK_EXIT_CHECK_FAILED; 341 } 342 } 343 quota2_create_inode(sblock, type); 344 } 345 346 dp = ginode(sblock->fs_quotafile[type]); 347 if (readblk(dp, 0, &hbp) != sblock->fs_bsize || 348 hbp->b_errs != 0) { 349 pfatal("can't re-read %s quota header\n", strtype); 350 exit(FSCK_EXIT_CHECK_FAILED); 351 } 352 q2h = (void *)hbp->b_un.b_buf; 353 filesize = iswap64(DIP(dp, size)); 354 nq2map = filesize / sblock->fs_bsize; 355 quotamap = malloc(sizeof(*quotamap) * nq2map); 356 /* map for full blocks */ 357 for (i = 0; i < nq2map; i++) { 358 nq2e = (sblock->fs_bsize - 359 ((i == 0) ? quota2_full_header_size : 0)) / sizeof(*q2e); 360 quotamap[i] = calloc(roundup(howmany(nq2e, NBBY), 361 sizeof(int16_t)), sizeof(char)); 362 for (j = 0; j < nq2e; j++) 363 setbit(quotamap[i], j); 364 } 365 366 /* check that all entries are in the lists (and only once) */ 367 i = -1; 368 ret = quota2_walk_list(dp, hbp, &q2h->q2h_free, &i, quota2_list_check); 369 if (ret) 370 return ret; 371 for (i = 0; i < quota2_hash_size; i++) { 372 ret = quota2_walk_list(dp, hbp, &q2h->q2h_entries[i], &i, 373 quota2_list_check); 374 if (ret) 375 return ret; 376 } 377 for (i = 0; i < nq2map; i++) { 378 nq2e = (sblock->fs_bsize - 379 ((i == 0) ? quota2_full_header_size : 0)) / sizeof(*q2e); 380 for (j = 0; j < nq2e; j++) { 381 if (!isset(quotamap[i], j)) 382 continue; 383 pwarn("QUOTA ENTRY NOT IN LIST"); 384 if (preen) 385 printf(" (FIXED)\n"); 386 else if (!reply("FIX")) { 387 markclean = 0; 388 break; 389 } 390 off = qindex2off( 391 (i == 0) ? quota2_full_header_size : 0, j); 392 if (i == 0) 393 bp = hbp; 394 else { 395 if (readblk(dp, i * sblock->fs_bsize, &bp) 396 != sblock->fs_bsize || bp->b_errs != 0) { 397 pfatal("can't read %s quota entry\n", 398 strtype); 399 break; 400 } 401 } 402 q2e = (void *)((caddr_t)(bp->b_un.b_buf) + off); 403 q2e->q2e_next = q2h->q2h_free; 404 q2h->q2h_free = iswap64(off + i * sblock->fs_bsize); 405 dirty(bp); 406 dirty(hbp); 407 if (bp != hbp) 408 bp->b_flags &= ~B_INUSE; 409 } 410 } 411 hbp->b_flags &= ~B_INUSE; 412 return 0; 413} 414 415/* compare/update on-disk usages to what we computed */ 416 417struct qcheck_arg { 418 const char *capstrtype; 419 struct uquot_hash *uquot_hash; 420}; 421 422static int quota2_list_qcheck(uint64_t *, struct quota2_entry *, uint64_t, 423 void *); 424static int 425quota2_list_qcheck(uint64_t *offp, struct quota2_entry *q2e, uint64_t off, 426 void *v) 427{ 428 uid_t uid = iswap32(q2e->q2e_uid); 429 struct qcheck_arg *a = v; 430 struct uquot *uq; 431 struct uquot uq_null; 432 433 memset(&uq_null, 0, sizeof(uq_null)); 434 435 uq = find_uquot(a->uquot_hash, uid, 0); 436 437 if (uq == NULL) 438 uq = &uq_null; 439 else 440 remove_uquot(a->uquot_hash, uq); 441 442 if (iswap64(q2e->q2e_val[QL_BLOCK].q2v_cur) == uq->uq_b && 443 iswap64(q2e->q2e_val[QL_FILE].q2v_cur) == uq->uq_i) 444 return 0; 445 pwarn("%s QUOTA MISMATCH FOR ID %d: %" PRIu64 "/%" PRIu64 " SHOULD BE " 446 "%" PRIu64 "/%" PRIu64, a->capstrtype, uid, 447 iswap64(q2e->q2e_val[QL_BLOCK].q2v_cur), 448 iswap64(q2e->q2e_val[QL_FILE].q2v_cur), uq->uq_b, uq->uq_i); 449 if (preen) { 450 printf(" (FIXED)\n"); 451 } else if (!reply("FIX")) { 452 markclean = 0; 453 return 0; 454 } 455 q2e->q2e_val[QL_BLOCK].q2v_cur = iswap64(uq->uq_b); 456 q2e->q2e_val[QL_FILE].q2v_cur = iswap64(uq->uq_i); 457 return Q2WL_DIRTY; 458} 459 460int 461quota2_check_usage(int type) 462{ 463 const char *strtype = (type == USRQUOTA) ? "user" : "group"; 464 const char *capstrtype = (type == USRQUOTA) ? "USER" : "GROUP"; 465 466 struct bufarea *hbp; 467 union dinode *dp; 468 struct quota2_header *q2h; 469 struct qcheck_arg a; 470 int i, ret; 471 const int quota2_hash_size = 1 << q2h_hash_shift; 472 473 if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(type)) == 0) 474 return 0; 475 476 a.capstrtype = capstrtype; 477 a.uquot_hash = 478 (type == USRQUOTA) ? uquot_user_hash : uquot_group_hash; 479 dp = ginode(sblock->fs_quotafile[type]); 480 if (readblk(dp, 0, &hbp) != sblock->fs_bsize || 481 hbp->b_errs != 0) { 482 pfatal("can't re-read %s quota header\n", strtype); 483 exit(FSCK_EXIT_CHECK_FAILED); 484 } 485 q2h = (void *)hbp->b_un.b_buf; 486 for (i = 0; i < quota2_hash_size; i++) { 487 ret = quota2_walk_list(dp, hbp, &q2h->q2h_entries[i], &a, 488 quota2_list_qcheck); 489 if (ret) 490 return ret; 491 } 492 493 for (i = 0; i < quota2_hash_size; i++) { 494 struct uquot *uq; 495 SLIST_FOREACH(uq, &a.uquot_hash[i], uq_entries) { 496 pwarn("%s QUOTA MISMATCH FOR ID %d: 0/0" 497 " SHOULD BE %" PRIu64 "/%" PRIu64, capstrtype, 498 uq->uq_uid, uq->uq_b, uq->uq_i); 499 if (preen) { 500 printf(" (ALLOCATED)\n"); 501 } else if (!reply("ALLOC")) { 502 markclean = 0; 503 return 0; 504 } 505 ret = quota2_alloc_quota(dp, hbp, 506 uq->uq_uid, uq->uq_b, uq->uq_i); 507 if (ret) 508 return ret; 509 } 510 } 511 hbp->b_flags &= ~B_INUSE; 512 return 0; 513} 514 515/* 516 * check if a quota check needs to be run, regardless of the clean flag 517 */ 518int 519quota2_check_doquota(void) 520{ 521 int retval = 1; 522 523 if ((sblock->fs_flags & FS_DOQUOTA2) == 0) 524 return 1; 525 if (sblock->fs_quota_magic != Q2_HEAD_MAGIC) { 526 pfatal("Invalid quota magic number\n"); 527 if (preen) 528 return 0; 529 if (reply("CONTINUE") == 0) { 530 exit(FSCK_EXIT_CHECK_FAILED); 531 } 532 return 0; 533 } 534 if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(USRQUOTA)) && 535 sblock->fs_quotafile[USRQUOTA] == 0) { 536 pwarn("no user quota inode\n"); 537 retval = 0; 538 } 539 if ((sblock->fs_quota_flags & FS_Q2_DO_TYPE(GRPQUOTA)) && 540 sblock->fs_quotafile[GRPQUOTA] == 0) { 541 pwarn("no group quota inode\n"); 542 retval = 0; 543 } 544 if (preen) 545 return retval; 546 if (!retval) { 547 if (reply("CONTINUE") == 0) { 548 exit(FSCK_EXIT_CHECK_FAILED); 549 } 550 return 0; 551 } 552 return 1; 553} 554