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