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