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