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