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