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