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