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