1 /* 2 * Copyright (c) 1980, 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Robert Elz at The University of Melbourne. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 static const char copyright[] = 39 "@(#) Copyright (c) 1980, 1990, 1993\n\ 40 The Regents of the University of California. All rights reserved.\n"; 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94"; 46 #endif 47 static const char rcsid[] = 48 "$FreeBSD$"; 49 #endif /* not lint */ 50 51 /* 52 * Fix up / report on disk quotas & usage 53 */ 54 #include <sys/param.h> 55 #include <sys/disklabel.h> 56 #include <sys/stat.h> 57 58 #include <ufs/ufs/dinode.h> 59 #include <ufs/ufs/quota.h> 60 #include <ufs/ffs/fs.h> 61 62 #include <err.h> 63 #include <errno.h> 64 #include <fcntl.h> 65 #include <fstab.h> 66 #include <grp.h> 67 #include <pwd.h> 68 #include <stdio.h> 69 #include <stdlib.h> 70 #include <string.h> 71 #include <unistd.h> 72 73 char *qfname = QUOTAFILENAME; 74 char *qfextension[] = INITQFNAMES; 75 char *quotagroup = QUOTAGROUP; 76 77 union { 78 struct fs sblk; 79 char dummy[MAXBSIZE]; 80 } un; 81 #define sblock un.sblk 82 long dev_bsize = 1; 83 ino_t maxino; 84 85 union dinode { 86 struct ufs1_dinode dp1; 87 struct ufs2_dinode dp2; 88 }; 89 #define DIP(dp, field) \ 90 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \ 91 (dp)->dp1.field : (dp)->dp2.field) 92 93 struct quotaname { 94 long flags; 95 char grpqfname[MAXPATHLEN + 1]; 96 char usrqfname[MAXPATHLEN + 1]; 97 }; 98 #define HASUSR 1 99 #define HASGRP 2 100 101 struct fileusage { 102 struct fileusage *fu_next; 103 u_long fu_curinodes; 104 u_long fu_curblocks; 105 u_long fu_id; 106 char fu_name[1]; 107 /* actually bigger */ 108 }; 109 #define FUHASH 1024 /* must be power of two */ 110 struct fileusage *fuhead[MAXQUOTAS][FUHASH]; 111 112 int aflag; /* all file systems */ 113 int gflag; /* check group quotas */ 114 int uflag; /* check user quotas */ 115 int vflag; /* verbose */ 116 int fi; /* open disk file descriptor */ 117 u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */ 118 119 struct fileusage * 120 addid(u_long, int, char *); 121 char *blockcheck(char *); 122 void bread(daddr_t, char *, long); 123 extern int checkfstab(int, int, void * (*)(struct fstab *), 124 int (*)(char *, char *, struct quotaname *)); 125 int chkquota(char *, char *, struct quotaname *); 126 void freeinodebuf(void); 127 union dinode * 128 getnextinode(ino_t); 129 int getquotagid(void); 130 int hasquota(struct fstab *, int, char **); 131 struct fileusage * 132 lookup(u_long, int); 133 void *needchk(struct fstab *); 134 int oneof(char *, char*[], int); 135 void resetinodebuf(void); 136 int update(char *, char *, int); 137 void usage(void); 138 139 int 140 main(argc, argv) 141 int argc; 142 char *argv[]; 143 { 144 struct fstab *fs; 145 struct passwd *pw; 146 struct group *gr; 147 struct quotaname *auxdata; 148 int i, argnum, maxrun, errs; 149 long done = 0; 150 char ch, *name; 151 152 errs = maxrun = 0; 153 while ((ch = getopt(argc, argv, "aguvl:")) != -1) { 154 switch(ch) { 155 case 'a': 156 aflag++; 157 break; 158 case 'g': 159 gflag++; 160 break; 161 case 'u': 162 uflag++; 163 break; 164 case 'v': 165 vflag++; 166 break; 167 case 'l': 168 maxrun = atoi(optarg); 169 break; 170 default: 171 usage(); 172 } 173 } 174 argc -= optind; 175 argv += optind; 176 if ((argc == 0 && !aflag) || (argc > 0 && aflag)) 177 usage(); 178 if (!gflag && !uflag) { 179 gflag++; 180 uflag++; 181 } 182 if (gflag) { 183 setgrent(); 184 while ((gr = getgrent()) != NULL) 185 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name); 186 endgrent(); 187 } 188 if (uflag) { 189 setpwent(); 190 while ((pw = getpwent()) != NULL) 191 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name); 192 endpwent(); 193 } 194 if (aflag) 195 exit(checkfstab(1, maxrun, needchk, chkquota)); 196 if (setfsent() == 0) 197 errx(1, "%s: can't open", FSTAB); 198 while ((fs = getfsent()) != NULL) { 199 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 || 200 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) && 201 (auxdata = needchk(fs)) && 202 (name = blockcheck(fs->fs_spec))) { 203 done |= 1 << argnum; 204 errs += chkquota(name, fs->fs_file, auxdata); 205 } 206 } 207 endfsent(); 208 for (i = 0; i < argc; i++) 209 if ((done & (1 << i)) == 0) 210 fprintf(stderr, "%s not found in %s\n", 211 argv[i], FSTAB); 212 exit(errs); 213 } 214 215 void 216 usage() 217 { 218 (void)fprintf(stderr, "%s\n%s\n", 219 "usage: quotacheck -a [-guv]", 220 " quotacheck [-guv] filesys ..."); 221 exit(1); 222 } 223 224 void * 225 needchk(fs) 226 struct fstab *fs; 227 { 228 struct quotaname *qnp; 229 char *qfnp; 230 231 if (strcmp(fs->fs_vfstype, "ufs") || 232 strcmp(fs->fs_type, FSTAB_RW)) 233 return (NULL); 234 if ((qnp = malloc(sizeof(*qnp))) == NULL) 235 errx(1, "malloc failed"); 236 qnp->flags = 0; 237 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) { 238 strcpy(qnp->grpqfname, qfnp); 239 qnp->flags |= HASGRP; 240 } 241 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) { 242 strcpy(qnp->usrqfname, qfnp); 243 qnp->flags |= HASUSR; 244 } 245 if (qnp->flags) 246 return (qnp); 247 free(qnp); 248 return (NULL); 249 } 250 251 /* 252 * Possible superblock locations ordered from most to least likely. 253 */ 254 static int sblock_try[] = SBLOCKSEARCH; 255 256 /* 257 * Scan the specified file system to check quota(s) present on it. 258 */ 259 int 260 chkquota(fsname, mntpt, qnp) 261 char *fsname, *mntpt; 262 struct quotaname *qnp; 263 { 264 struct fileusage *fup; 265 union dinode *dp; 266 int cg, i, mode, errs = 0; 267 ino_t ino; 268 269 if ((fi = open(fsname, O_RDONLY, 0)) < 0) { 270 warn("%s", fsname); 271 return (1); 272 } 273 if (vflag) { 274 (void)printf("*** Checking "); 275 if (qnp->flags & HASUSR) 276 (void)printf("%s%s", qfextension[USRQUOTA], 277 (qnp->flags & HASGRP) ? " and " : ""); 278 if (qnp->flags & HASGRP) 279 (void)printf("%s", qfextension[GRPQUOTA]); 280 (void)printf(" quotas for %s (%s)\n", fsname, mntpt); 281 } 282 sync(); 283 dev_bsize = 1; 284 for (i = 0; sblock_try[i] != -1; i++) { 285 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE); 286 if ((sblock.fs_magic == FS_UFS1_MAGIC || 287 (sblock.fs_magic == FS_UFS2_MAGIC && 288 sblock.fs_sblockloc == 289 numfrags(&sblock, sblock_try[i]))) && 290 sblock.fs_bsize <= MAXBSIZE && 291 sblock.fs_bsize >= sizeof(struct fs)) 292 break; 293 } 294 if (sblock_try[i] == -1) { 295 warn("Cannot find file system superblock"); 296 return (1); 297 } 298 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1); 299 maxino = sblock.fs_ncg * sblock.fs_ipg; 300 resetinodebuf(); 301 for (ino = 0, cg = 0; cg < sblock.fs_ncg; cg++) { 302 for (i = 0; i < sblock.fs_ipg; i++, ino++) { 303 if (ino < ROOTINO) 304 continue; 305 if ((dp = getnextinode(ino)) == NULL) 306 continue; 307 if ((mode = DIP(dp, di_mode) & IFMT) == 0) 308 continue; 309 if (qnp->flags & HASGRP) { 310 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA, 311 (char *)0); 312 fup->fu_curinodes++; 313 if (mode == IFREG || mode == IFDIR || 314 mode == IFLNK) 315 fup->fu_curblocks += DIP(dp, di_blocks); 316 } 317 if (qnp->flags & HASUSR) { 318 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA, 319 (char *)0); 320 fup->fu_curinodes++; 321 if (mode == IFREG || mode == IFDIR || 322 mode == IFLNK) 323 fup->fu_curblocks += DIP(dp, di_blocks); 324 } 325 } 326 } 327 freeinodebuf(); 328 if (qnp->flags & HASUSR) 329 errs += update(mntpt, qnp->usrqfname, USRQUOTA); 330 if (qnp->flags & HASGRP) 331 errs += update(mntpt, qnp->grpqfname, GRPQUOTA); 332 close(fi); 333 return (errs); 334 } 335 336 /* 337 * Update a specified quota file. 338 */ 339 int 340 update(fsname, quotafile, type) 341 char *fsname, *quotafile; 342 int type; 343 { 344 struct fileusage *fup; 345 FILE *qfi, *qfo; 346 u_long id, lastid; 347 off_t offset; 348 struct dqblk dqbuf; 349 static int warned = 0; 350 static struct dqblk zerodqbuf; 351 static struct fileusage zerofileusage; 352 353 if ((qfo = fopen(quotafile, "r+")) == NULL) { 354 if (errno == ENOENT) 355 qfo = fopen(quotafile, "w+"); 356 if (qfo) { 357 warnx("creating quota file %s", quotafile); 358 #define MODE (S_IRUSR|S_IWUSR|S_IRGRP) 359 (void) fchown(fileno(qfo), getuid(), getquotagid()); 360 (void) fchmod(fileno(qfo), MODE); 361 } else { 362 warn("%s", quotafile); 363 return (1); 364 } 365 } 366 if ((qfi = fopen(quotafile, "r")) == NULL) { 367 warn("%s", quotafile); 368 (void) fclose(qfo); 369 return (1); 370 } 371 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 && 372 errno == EOPNOTSUPP && !warned && vflag) { 373 warned++; 374 (void)printf("*** Warning: %s\n", 375 "Quotas are not compiled into this kernel"); 376 } 377 for (lastid = highid[type], id = 0, offset = 0; id <= lastid; 378 id++, offset += sizeof(struct dqblk)) { 379 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0) 380 dqbuf = zerodqbuf; 381 if ((fup = lookup(id, type)) == 0) 382 fup = &zerofileusage; 383 if (dqbuf.dqb_curinodes == fup->fu_curinodes && 384 dqbuf.dqb_curblocks == fup->fu_curblocks) { 385 fup->fu_curinodes = 0; 386 fup->fu_curblocks = 0; 387 continue; 388 } 389 if (vflag) { 390 if (aflag) 391 printf("%s: ", fsname); 392 printf("%-8s fixed:", fup->fu_name); 393 if (dqbuf.dqb_curinodes != fup->fu_curinodes) 394 (void)printf("\tinodes %lu -> %lu", 395 (u_long)dqbuf.dqb_curinodes, 396 (u_long)fup->fu_curinodes); 397 if (dqbuf.dqb_curblocks != fup->fu_curblocks) 398 (void)printf("\tblocks %lu -> %lu", 399 (u_long)dqbuf.dqb_curblocks, 400 (u_long)fup->fu_curblocks); 401 (void)printf("\n"); 402 } 403 /* 404 * Reset time limit if have a soft limit and were 405 * previously under it, but are now over it. 406 */ 407 if (dqbuf.dqb_bsoftlimit && 408 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit && 409 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit) 410 dqbuf.dqb_btime = 0; 411 if (dqbuf.dqb_isoftlimit && 412 dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit && 413 fup->fu_curblocks >= dqbuf.dqb_isoftlimit) 414 dqbuf.dqb_itime = 0; 415 dqbuf.dqb_curinodes = fup->fu_curinodes; 416 dqbuf.dqb_curblocks = fup->fu_curblocks; 417 if (fseek(qfo, offset, SEEK_SET) < 0) { 418 warn("%s: seek failed", quotafile); 419 return(1); 420 } 421 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo); 422 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id, 423 (caddr_t)&dqbuf); 424 fup->fu_curinodes = 0; 425 fup->fu_curblocks = 0; 426 } 427 fclose(qfi); 428 fflush(qfo); 429 ftruncate(fileno(qfo), 430 (((off_t)highid[type] + 1) * sizeof(struct dqblk))); 431 fclose(qfo); 432 return (0); 433 } 434 435 /* 436 * Check to see if target appears in list of size cnt. 437 */ 438 int 439 oneof(target, list, cnt) 440 char *target, *list[]; 441 int cnt; 442 { 443 int i; 444 445 for (i = 0; i < cnt; i++) 446 if (strcmp(target, list[i]) == 0) 447 return (i); 448 return (-1); 449 } 450 451 /* 452 * Determine the group identifier for quota files. 453 */ 454 int 455 getquotagid() 456 { 457 struct group *gr; 458 459 if ((gr = getgrnam(quotagroup)) != NULL) 460 return (gr->gr_gid); 461 return (-1); 462 } 463 464 /* 465 * Check to see if a particular quota is to be enabled. 466 */ 467 int 468 hasquota(fs, type, qfnamep) 469 struct fstab *fs; 470 int type; 471 char **qfnamep; 472 { 473 char *opt; 474 char *cp; 475 static char initname, usrname[100], grpname[100]; 476 static char buf[BUFSIZ]; 477 478 if (!initname) { 479 (void)snprintf(usrname, sizeof(usrname), 480 "%s%s", qfextension[USRQUOTA], qfname); 481 (void)snprintf(grpname, sizeof(grpname), 482 "%s%s", qfextension[GRPQUOTA], qfname); 483 initname = 1; 484 } 485 strcpy(buf, fs->fs_mntops); 486 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 487 if ((cp = index(opt, '=')) != NULL) 488 *cp++ = '\0'; 489 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 490 break; 491 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 492 break; 493 } 494 if (!opt) 495 return (0); 496 if (cp) 497 *qfnamep = cp; 498 else { 499 (void)snprintf(buf, sizeof(buf), 500 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 501 *qfnamep = buf; 502 } 503 return (1); 504 } 505 506 /* 507 * Routines to manage the file usage table. 508 * 509 * Lookup an id of a specific type. 510 */ 511 struct fileusage * 512 lookup(id, type) 513 u_long id; 514 int type; 515 { 516 struct fileusage *fup; 517 518 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 519 if (fup->fu_id == id) 520 return (fup); 521 return (NULL); 522 } 523 524 /* 525 * Add a new file usage id if it does not already exist. 526 */ 527 struct fileusage * 528 addid(id, type, name) 529 u_long id; 530 int type; 531 char *name; 532 { 533 struct fileusage *fup, **fhp; 534 int len; 535 536 if ((fup = lookup(id, type)) != NULL) 537 return (fup); 538 if (name) 539 len = strlen(name); 540 else 541 len = 10; 542 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 543 errx(1, "calloc failed"); 544 fhp = &fuhead[type][id & (FUHASH - 1)]; 545 fup->fu_next = *fhp; 546 *fhp = fup; 547 fup->fu_id = id; 548 if (id > highid[type]) 549 highid[type] = id; 550 if (name) 551 bcopy(name, fup->fu_name, len + 1); 552 else { 553 (void)sprintf(fup->fu_name, "%lu", id); 554 if (vflag) 555 printf("unknown %cid: %lu\n", 556 type == USRQUOTA ? 'u' : 'g', id); 557 } 558 return (fup); 559 } 560 561 /* 562 * Special purpose version of ginode used to optimize pass 563 * over all the inodes in numerical order. 564 */ 565 static ino_t nextino, lastinum, lastvalidinum; 566 static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 567 static caddr_t inodebuf; 568 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 569 570 union dinode * 571 getnextinode(inumber) 572 ino_t inumber; 573 { 574 long size; 575 ufs2_daddr_t dblk; 576 union dinode *dp; 577 static caddr_t nextinop; 578 579 if (inumber != nextino++ || inumber > lastvalidinum) 580 errx(1, "bad inode number %d to nextinode", inumber); 581 if (inumber >= lastinum) { 582 readcnt++; 583 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 584 if (readcnt % readpercg == 0) { 585 size = partialsize; 586 lastinum += partialcnt; 587 } else { 588 size = inobufsize; 589 lastinum += fullcnt; 590 } 591 /* 592 * If bread returns an error, it will already have zeroed 593 * out the buffer, so we do not need to do so here. 594 */ 595 bread(dblk, inodebuf, size); 596 nextinop = inodebuf; 597 } 598 dp = (union dinode *)nextinop; 599 if (sblock.fs_magic == FS_UFS1_MAGIC) 600 nextinop += sizeof(struct ufs1_dinode); 601 else 602 nextinop += sizeof(struct ufs2_dinode); 603 return (dp); 604 } 605 606 /* 607 * Prepare to scan a set of inodes. 608 */ 609 void 610 resetinodebuf() 611 { 612 613 nextino = 0; 614 lastinum = 0; 615 readcnt = 0; 616 inobufsize = blkroundup(&sblock, INOBUFSIZE); 617 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ? 618 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 619 readpercg = sblock.fs_ipg / fullcnt; 620 partialcnt = sblock.fs_ipg % fullcnt; 621 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ? 622 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 623 if (partialcnt != 0) { 624 readpercg++; 625 } else { 626 partialcnt = fullcnt; 627 partialsize = inobufsize; 628 } 629 if (inodebuf == NULL && 630 (inodebuf = malloc((u_int)inobufsize)) == NULL) 631 errx(1, "malloc failed"); 632 while (nextino < ROOTINO) 633 getnextinode(nextino); 634 } 635 636 /* 637 * Free up data structures used to scan inodes. 638 */ 639 void 640 freeinodebuf() 641 { 642 643 if (inodebuf != NULL) 644 free(inodebuf); 645 inodebuf = NULL; 646 } 647 648 /* 649 * Read specified disk blocks. 650 */ 651 void 652 bread(bno, buf, cnt) 653 daddr_t bno; 654 char *buf; 655 long cnt; 656 { 657 658 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 659 read(fi, buf, cnt) != cnt) 660 errx(1, "block %ld", (long)bno); 661 } 662