quotacheck.c revision 9273
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 dev_bsize = 1; 264 bread(SBOFF, (char *)&sblock, (long)SBSIZE); 265 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1); 266 maxino = sblock.fs_ncg * sblock.fs_ipg; 267 resetinodebuf(); 268 for (ino = 0, cg = 0; cg < sblock.fs_ncg; cg++) { 269 for (i = 0; i < sblock.fs_ipg; i++, ino++) { 270 if (ino < ROOTINO) 271 continue; 272 if ((dp = getnextinode(ino)) == NULL) 273 continue; 274 if ((mode = dp->di_mode & IFMT) == 0) 275 continue; 276 if (qnp->flags & HASGRP) { 277 fup = addid((u_long)dp->di_gid, GRPQUOTA, 278 (char *)0); 279 fup->fu_curinodes++; 280 if (mode == IFREG || mode == IFDIR || 281 mode == IFLNK) 282 fup->fu_curblocks += dp->di_blocks; 283 } 284 if (qnp->flags & HASUSR) { 285 fup = addid((u_long)dp->di_uid, USRQUOTA, 286 (char *)0); 287 fup->fu_curinodes++; 288 if (mode == IFREG || mode == IFDIR || 289 mode == IFLNK) 290 fup->fu_curblocks += dp->di_blocks; 291 } 292 } 293 } 294 freeinodebuf(); 295 if (qnp->flags & HASUSR) 296 errs += update(mntpt, qnp->usrqfname, USRQUOTA); 297 if (qnp->flags & HASGRP) 298 errs += update(mntpt, qnp->grpqfname, GRPQUOTA); 299 close(fi); 300 return (errs); 301} 302 303/* 304 * Update a specified quota file. 305 */ 306int 307update(fsname, quotafile, type) 308 char *fsname, *quotafile; 309 register int type; 310{ 311 register struct fileusage *fup; 312 register FILE *qfi, *qfo; 313 register u_long id, lastid; 314 struct dqblk dqbuf; 315 static int warned = 0; 316 static struct dqblk zerodqbuf; 317 static struct fileusage zerofileusage; 318 319 if ((qfo = fopen(quotafile, "r+")) == NULL) { 320 if (errno == ENOENT) 321 qfo = fopen(quotafile, "w+"); 322 if (qfo) { 323 (void) fprintf(stderr, 324 "quotacheck: creating quota file %s\n", quotafile); 325#define MODE (S_IRUSR|S_IWUSR|S_IRGRP) 326 (void) fchown(fileno(qfo), getuid(), getquotagid()); 327 (void) fchmod(fileno(qfo), MODE); 328 } else { 329 (void) fprintf(stderr, 330 "quotacheck: %s: %s\n", quotafile, strerror(errno)); 331 return (1); 332 } 333 } 334 if ((qfi = fopen(quotafile, "r")) == NULL) { 335 (void) fprintf(stderr, 336 "quotacheck: %s: %s\n", quotafile, strerror(errno)); 337 (void) fclose(qfo); 338 return (1); 339 } 340 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 && 341 errno == EOPNOTSUPP && !warned && vflag) { 342 warned++; 343 (void)printf("*** Warning: %s\n", 344 "Quotas are not compiled into this kernel"); 345 } 346 for (lastid = highid[type], id = 0; id <= lastid; id++) { 347 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0) 348 dqbuf = zerodqbuf; 349 if ((fup = lookup(id, type)) == 0) 350 fup = &zerofileusage; 351 if (dqbuf.dqb_curinodes == fup->fu_curinodes && 352 dqbuf.dqb_curblocks == fup->fu_curblocks) { 353 fup->fu_curinodes = 0; 354 fup->fu_curblocks = 0; 355 fseek(qfo, (long)sizeof(struct dqblk), 1); 356 continue; 357 } 358 if (vflag) { 359 if (aflag) 360 printf("%s: ", fsname); 361 printf("%-8s fixed:", fup->fu_name); 362 if (dqbuf.dqb_curinodes != fup->fu_curinodes) 363 (void)printf("\tinodes %d -> %d", 364 dqbuf.dqb_curinodes, fup->fu_curinodes); 365 if (dqbuf.dqb_curblocks != fup->fu_curblocks) 366 (void)printf("\tblocks %d -> %d", 367 dqbuf.dqb_curblocks, fup->fu_curblocks); 368 (void)printf("\n"); 369 } 370 /* 371 * Reset time limit if have a soft limit and were 372 * previously under it, but are now over it. 373 */ 374 if (dqbuf.dqb_bsoftlimit && 375 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit && 376 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit) 377 dqbuf.dqb_btime = 0; 378 if (dqbuf.dqb_isoftlimit && 379 dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit && 380 fup->fu_curblocks >= dqbuf.dqb_isoftlimit) 381 dqbuf.dqb_itime = 0; 382 dqbuf.dqb_curinodes = fup->fu_curinodes; 383 dqbuf.dqb_curblocks = fup->fu_curblocks; 384 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo); 385 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id, 386 (caddr_t)&dqbuf); 387 fup->fu_curinodes = 0; 388 fup->fu_curblocks = 0; 389 } 390 fclose(qfi); 391 fflush(qfo); 392 ftruncate(fileno(qfo), 393 (off_t)((highid[type] + 1) * sizeof(struct dqblk))); 394 fclose(qfo); 395 return (0); 396} 397 398/* 399 * Check to see if target appears in list of size cnt. 400 */ 401int 402oneof(target, list, cnt) 403 register char *target, *list[]; 404 int cnt; 405{ 406 register int i; 407 408 for (i = 0; i < cnt; i++) 409 if (strcmp(target, list[i]) == 0) 410 return (i); 411 return (-1); 412} 413 414/* 415 * Determine the group identifier for quota files. 416 */ 417int 418getquotagid() 419{ 420 struct group *gr; 421 422 if (gr = getgrnam(quotagroup)) 423 return (gr->gr_gid); 424 return (-1); 425} 426 427/* 428 * Check to see if a particular quota is to be enabled. 429 */ 430int 431hasquota(fs, type, qfnamep) 432 register struct fstab *fs; 433 int type; 434 char **qfnamep; 435{ 436 register char *opt; 437 char *cp; 438 static char initname, usrname[100], grpname[100]; 439 static char buf[BUFSIZ]; 440 441 if (!initname) { 442 (void)snprintf(usrname, sizeof(usrname), 443 "%s%s", qfextension[USRQUOTA], qfname); 444 (void)snprintf(grpname, sizeof(grpname), 445 "%s%s", qfextension[GRPQUOTA], qfname); 446 initname = 1; 447 } 448 strcpy(buf, fs->fs_mntops); 449 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 450 if (cp = index(opt, '=')) 451 *cp++ = '\0'; 452 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 453 break; 454 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 455 break; 456 } 457 if (!opt) 458 return (0); 459 if (cp) 460 *qfnamep = cp; 461 else { 462 (void)snprintf(buf, sizeof(buf), 463 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 464 *qfnamep = buf; 465 } 466 return (1); 467} 468 469/* 470 * Routines to manage the file usage table. 471 * 472 * Lookup an id of a specific type. 473 */ 474struct fileusage * 475lookup(id, type) 476 u_long id; 477 int type; 478{ 479 register struct fileusage *fup; 480 481 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 482 if (fup->fu_id == id) 483 return (fup); 484 return (NULL); 485} 486 487/* 488 * Add a new file usage id if it does not already exist. 489 */ 490struct fileusage * 491addid(id, type, name) 492 u_long id; 493 int type; 494 char *name; 495{ 496 struct fileusage *fup, **fhp; 497 int len; 498 499 if (fup = lookup(id, type)) 500 return (fup); 501 if (name) 502 len = strlen(name); 503 else 504 len = 10; 505 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 506 err("%s", strerror(errno)); 507 fhp = &fuhead[type][id & (FUHASH - 1)]; 508 fup->fu_next = *fhp; 509 *fhp = fup; 510 fup->fu_id = id; 511 if (id > highid[type]) 512 highid[type] = id; 513 if (name) 514 bcopy(name, fup->fu_name, len + 1); 515 else 516 (void)sprintf(fup->fu_name, "%u", id); 517 return (fup); 518} 519 520/* 521 * Special purpose version of ginode used to optimize pass 522 * over all the inodes in numerical order. 523 */ 524ino_t nextino, lastinum; 525long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 526struct dinode *inodebuf; 527#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 528 529struct dinode * 530getnextinode(inumber) 531 ino_t inumber; 532{ 533 long size; 534 daddr_t dblk; 535 static struct dinode *dp; 536 537 if (inumber != nextino++ || inumber > maxino) 538 err("bad inode number %d to nextinode", inumber); 539 if (inumber >= lastinum) { 540 readcnt++; 541 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 542 if (readcnt % readpercg == 0) { 543 size = partialsize; 544 lastinum += partialcnt; 545 } else { 546 size = inobufsize; 547 lastinum += fullcnt; 548 } 549 bread(dblk, (char *)inodebuf, size); 550 dp = inodebuf; 551 } 552 return (dp++); 553} 554 555/* 556 * Prepare to scan a set of inodes. 557 */ 558void 559resetinodebuf() 560{ 561 562 nextino = 0; 563 lastinum = 0; 564 readcnt = 0; 565 inobufsize = blkroundup(&sblock, INOBUFSIZE); 566 fullcnt = inobufsize / sizeof(struct dinode); 567 readpercg = sblock.fs_ipg / fullcnt; 568 partialcnt = sblock.fs_ipg % fullcnt; 569 partialsize = partialcnt * sizeof(struct dinode); 570 if (partialcnt != 0) { 571 readpercg++; 572 } else { 573 partialcnt = fullcnt; 574 partialsize = inobufsize; 575 } 576 if (inodebuf == NULL && 577 (inodebuf = malloc((u_int)inobufsize)) == NULL) 578 err("%s", strerror(errno)); 579 while (nextino < ROOTINO) 580 getnextinode(nextino); 581} 582 583/* 584 * Free up data structures used to scan inodes. 585 */ 586void 587freeinodebuf() 588{ 589 590 if (inodebuf != NULL) 591 free(inodebuf); 592 inodebuf = NULL; 593} 594 595/* 596 * Read specified disk blocks. 597 */ 598void 599bread(bno, buf, cnt) 600 daddr_t bno; 601 char *buf; 602 long cnt; 603{ 604 605 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 606 read(fi, buf, cnt) != cnt) 607 err("block %ld", bno); 608} 609 610#if __STDC__ 611#include <stdarg.h> 612#else 613#include <varargs.h> 614#endif 615 616void 617#if __STDC__ 618err(const char *fmt, ...) 619#else 620err(fmt, va_alist) 621 char *fmt; 622 va_dcl 623#endif 624{ 625 va_list ap; 626#if __STDC__ 627 va_start(ap, fmt); 628#else 629 va_start(ap); 630#endif 631 (void)fprintf(stderr, "quotacheck: "); 632 (void)vfprintf(stderr, fmt, ap); 633 va_end(ap); 634 (void)fprintf(stderr, "\n"); 635 exit(1); 636 /* NOTREACHED */ 637} 638