quotacheck.c revision 37239
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: quotacheck.c,v 1.8 1997/06/30 11:08:29 charnier Exp $ 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 %lu -> %lu", 365 (u_long)dqbuf.dqb_curinodes, 366 (u_long)fup->fu_curinodes); 367 if (dqbuf.dqb_curblocks != fup->fu_curblocks) 368 (void)printf("\tblocks %lu -> %lu", 369 (u_long)dqbuf.dqb_curblocks, 370 (u_long)fup->fu_curblocks); 371 (void)printf("\n"); 372 } 373 /* 374 * Reset time limit if have a soft limit and were 375 * previously under it, but are now over it. 376 */ 377 if (dqbuf.dqb_bsoftlimit && 378 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit && 379 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit) 380 dqbuf.dqb_btime = 0; 381 if (dqbuf.dqb_isoftlimit && 382 dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit && 383 fup->fu_curblocks >= dqbuf.dqb_isoftlimit) 384 dqbuf.dqb_itime = 0; 385 dqbuf.dqb_curinodes = fup->fu_curinodes; 386 dqbuf.dqb_curblocks = fup->fu_curblocks; 387 if (fseek(qfo, (long)offset, SEEK_SET) < 0) { 388 warn("%s: seek failed", quotafile); 389 return(1); 390 } 391 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo); 392 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id, 393 (caddr_t)&dqbuf); 394 fup->fu_curinodes = 0; 395 fup->fu_curblocks = 0; 396 } 397 fclose(qfi); 398 fflush(qfo); 399 ftruncate(fileno(qfo), 400 (off_t)((highid[type] + 1) * sizeof(struct dqblk))); 401 fclose(qfo); 402 return (0); 403} 404 405/* 406 * Check to see if target appears in list of size cnt. 407 */ 408int 409oneof(target, list, cnt) 410 register char *target, *list[]; 411 int cnt; 412{ 413 register int i; 414 415 for (i = 0; i < cnt; i++) 416 if (strcmp(target, list[i]) == 0) 417 return (i); 418 return (-1); 419} 420 421/* 422 * Determine the group identifier for quota files. 423 */ 424int 425getquotagid() 426{ 427 struct group *gr; 428 429 if ((gr = getgrnam(quotagroup)) != NULL) 430 return (gr->gr_gid); 431 return (-1); 432} 433 434/* 435 * Check to see if a particular quota is to be enabled. 436 */ 437int 438hasquota(fs, type, qfnamep) 439 register struct fstab *fs; 440 int type; 441 char **qfnamep; 442{ 443 register char *opt; 444 char *cp; 445 static char initname, usrname[100], grpname[100]; 446 static char buf[BUFSIZ]; 447 448 if (!initname) { 449 (void)snprintf(usrname, sizeof(usrname), 450 "%s%s", qfextension[USRQUOTA], qfname); 451 (void)snprintf(grpname, sizeof(grpname), 452 "%s%s", qfextension[GRPQUOTA], qfname); 453 initname = 1; 454 } 455 strcpy(buf, fs->fs_mntops); 456 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 457 if ((cp = index(opt, '=')) != NULL) 458 *cp++ = '\0'; 459 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 460 break; 461 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 462 break; 463 } 464 if (!opt) 465 return (0); 466 if (cp) 467 *qfnamep = cp; 468 else { 469 (void)snprintf(buf, sizeof(buf), 470 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 471 *qfnamep = buf; 472 } 473 return (1); 474} 475 476/* 477 * Routines to manage the file usage table. 478 * 479 * Lookup an id of a specific type. 480 */ 481struct fileusage * 482lookup(id, type) 483 u_long id; 484 int type; 485{ 486 register struct fileusage *fup; 487 488 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 489 if (fup->fu_id == id) 490 return (fup); 491 return (NULL); 492} 493 494/* 495 * Add a new file usage id if it does not already exist. 496 */ 497struct fileusage * 498addid(id, type, name) 499 u_long id; 500 int type; 501 char *name; 502{ 503 struct fileusage *fup, **fhp; 504 int len; 505 506 if ((fup = lookup(id, type)) != NULL) 507 return (fup); 508 if (name) 509 len = strlen(name); 510 else 511 len = 10; 512 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 513 err(1, NULL); 514 fhp = &fuhead[type][id & (FUHASH - 1)]; 515 fup->fu_next = *fhp; 516 *fhp = fup; 517 fup->fu_id = id; 518 if (id > highid[type]) 519 highid[type] = id; 520 if (name) 521 bcopy(name, fup->fu_name, len + 1); 522 else { 523 (void)sprintf(fup->fu_name, "%lu", id); 524 if (vflag) 525 printf("unknown %cid: %lu\n", 526 type == USRQUOTA ? 'u' : 'g', id); 527 } 528 return (fup); 529} 530 531/* 532 * Special purpose version of ginode used to optimize pass 533 * over all the inodes in numerical order. 534 */ 535ino_t nextino, lastinum; 536long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 537struct dinode *inodebuf; 538#define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 539 540struct dinode * 541getnextinode(inumber) 542 ino_t inumber; 543{ 544 long size; 545 daddr_t dblk; 546 static struct dinode *dp; 547 548 if (inumber != nextino++ || inumber > maxino) 549 errx(1, "bad inode number %d to nextinode", inumber); 550 if (inumber >= lastinum) { 551 readcnt++; 552 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 553 if (readcnt % readpercg == 0) { 554 size = partialsize; 555 lastinum += partialcnt; 556 } else { 557 size = inobufsize; 558 lastinum += fullcnt; 559 } 560 bread(dblk, (char *)inodebuf, size); 561 dp = inodebuf; 562 } 563 return (dp++); 564} 565 566/* 567 * Prepare to scan a set of inodes. 568 */ 569void 570resetinodebuf() 571{ 572 573 nextino = 0; 574 lastinum = 0; 575 readcnt = 0; 576 inobufsize = blkroundup(&sblock, INOBUFSIZE); 577 fullcnt = inobufsize / sizeof(struct dinode); 578 readpercg = sblock.fs_ipg / fullcnt; 579 partialcnt = sblock.fs_ipg % fullcnt; 580 partialsize = partialcnt * sizeof(struct dinode); 581 if (partialcnt != 0) { 582 readpercg++; 583 } else { 584 partialcnt = fullcnt; 585 partialsize = inobufsize; 586 } 587 if (inodebuf == NULL && 588 (inodebuf = malloc((u_int)inobufsize)) == NULL) 589 err(1, NULL); 590 while (nextino < ROOTINO) 591 getnextinode(nextino); 592} 593 594/* 595 * Free up data structures used to scan inodes. 596 */ 597void 598freeinodebuf() 599{ 600 601 if (inodebuf != NULL) 602 free(inodebuf); 603 inodebuf = NULL; 604} 605 606/* 607 * Read specified disk blocks. 608 */ 609void 610bread(bno, buf, cnt) 611 daddr_t bno; 612 char *buf; 613 long cnt; 614{ 615 616 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 617 read(fi, buf, cnt) != cnt) 618 errx(1, "block %ld", (long)bno); 619} 620