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