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