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/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 73 char *qfname = QUOTAFILENAME; 74 char *qfextension[] = INITQFNAMES; 75 char *quotagroup = QUOTAGROUP; 76 77 union { 78 struct fs sblk; 79 char dummy[MAXBSIZE]; 80 } un; 81 #define sblock un.sblk 82 long dev_bsize = 1; 83 ino_t maxino; 84 85 struct quotaname { 86 long flags; 87 char grpqfname[MAXPATHLEN + 1]; 88 char usrqfname[MAXPATHLEN + 1]; 89 }; 90 #define HASUSR 1 91 #define HASGRP 2 92 93 struct 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 */ 102 struct fileusage *fuhead[MAXQUOTAS][FUHASH]; 103 104 int aflag; /* all filesystems */ 105 int gflag; /* check group quotas */ 106 int uflag; /* check user quotas */ 107 int vflag; /* verbose */ 108 int fi; /* open disk file descriptor */ 109 u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */ 110 111 struct fileusage * 112 addid(u_long, int, char *); 113 char *blockcheck(char *); 114 void bread(daddr_t, char *, long); 115 extern int checkfstab(int, int, void * (*)(struct fstab *), 116 int (*)(char *, char *, struct quotaname *)); 117 int chkquota(char *, char *, struct quotaname *); 118 void freeinodebuf(void); 119 struct dinode * 120 getnextinode(ino_t); 121 int getquotagid(void); 122 int hasquota(struct fstab *, int, char **); 123 struct fileusage * 124 lookup(u_long, int); 125 void *needchk(struct fstab *); 126 int oneof(char *, char*[], int); 127 void resetinodebuf(void); 128 int update(char *, char *, int); 129 void usage(void); 130 131 int 132 main(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 207 void 208 usage() 209 { 210 (void)fprintf(stderr, "%s\n%s\n", 211 "usage: quotacheck -a [-guv]", 212 " quotacheck [-guv] filesys ..."); 213 exit(1); 214 } 215 216 void * 217 needchk(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 */ 246 int 247 chkquota(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 */ 313 int 314 update(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 */ 412 int 413 oneof(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 */ 428 int 429 getquotagid() 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 */ 441 int 442 hasquota(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 */ 485 struct fileusage * 486 lookup(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 */ 501 struct fileusage * 502 addid(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 */ 539 ino_t nextino, lastinum; 540 long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 541 struct dinode *inodebuf; 542 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 543 544 struct dinode * 545 getnextinode(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 */ 573 void 574 resetinodebuf() 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 */ 601 void 602 freeinodebuf() 603 { 604 605 if (inodebuf != NULL) 606 free(inodebuf); 607 inodebuf = NULL; 608 } 609 610 /* 611 * Read specified disk blocks. 612 */ 613 void 614 bread(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