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 * @(#) Copyright (c) 1980, 1990, 1993 The Regents of the University of California. All rights reserved. 37 * @(#)quotacheck.c 8.3 (Berkeley) 1/29/94 38 * $FreeBSD: src/sbin/quotacheck/quotacheck.c,v 1.11 1999/08/28 00:14:01 peter Exp $ 39 * $DragonFly: src/sbin/quotacheck/quotacheck.c,v 1.2 2003/06/17 04:27:34 dillon Exp $ 40 */ 41 42 /* 43 * Fix up / report on disk quotas & usage 44 */ 45 #include <sys/param.h> 46 #include <sys/stat.h> 47 48 #include <ufs/ufs/dinode.h> 49 #include <ufs/ufs/quota.h> 50 #include <ufs/ffs/fs.h> 51 52 #include <err.h> 53 #include <errno.h> 54 #include <fcntl.h> 55 #include <fstab.h> 56 #include <grp.h> 57 #include <pwd.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #include <unistd.h> 62 63 char *qfname = QUOTAFILENAME; 64 char *qfextension[] = INITQFNAMES; 65 char *quotagroup = QUOTAGROUP; 66 67 union { 68 struct fs sblk; 69 char dummy[MAXBSIZE]; 70 } un; 71 #define sblock un.sblk 72 long dev_bsize = 1; 73 long maxino; 74 75 struct quotaname { 76 long flags; 77 char grpqfname[MAXPATHLEN + 1]; 78 char usrqfname[MAXPATHLEN + 1]; 79 }; 80 #define HASUSR 1 81 #define HASGRP 2 82 83 struct fileusage { 84 struct fileusage *fu_next; 85 u_long fu_curinodes; 86 u_long fu_curblocks; 87 u_long fu_id; 88 char fu_name[1]; 89 /* actually bigger */ 90 }; 91 #define FUHASH 1024 /* must be power of two */ 92 struct fileusage *fuhead[MAXQUOTAS][FUHASH]; 93 94 int aflag; /* all file systems */ 95 int gflag; /* check group quotas */ 96 int uflag; /* check user quotas */ 97 int vflag; /* verbose */ 98 int fi; /* open disk file descriptor */ 99 u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */ 100 101 struct fileusage * 102 addid __P((u_long, int, char *)); 103 char *blockcheck __P((char *)); 104 void bread __P((daddr_t, char *, long)); 105 extern int checkfstab __P((int, int, int (*)(struct fstab *), 106 int (*)(char *, char *, long, int))); 107 int chkquota __P((char *, char *, struct quotaname *)); 108 void freeinodebuf __P((void)); 109 struct dinode * 110 getnextinode __P((ino_t)); 111 int getquotagid __P((void)); 112 int hasquota __P((struct fstab *, int, char **)); 113 struct fileusage * 114 lookup __P((u_long, int)); 115 void *needchk __P((struct fstab *)); 116 int oneof __P((char *, char*[], int)); 117 void resetinodebuf __P((void)); 118 int update __P((char *, char *, int)); 119 void usage __P((void)); 120 121 int 122 main(argc, argv) 123 int argc; 124 char *argv[]; 125 { 126 register struct fstab *fs; 127 register struct passwd *pw; 128 register struct group *gr; 129 struct quotaname *auxdata; 130 int i, argnum, maxrun, errs; 131 long done = 0; 132 char ch, *name; 133 134 errs = maxrun = 0; 135 while ((ch = getopt(argc, argv, "aguvl:")) != -1) { 136 switch(ch) { 137 case 'a': 138 aflag++; 139 break; 140 case 'g': 141 gflag++; 142 break; 143 case 'u': 144 uflag++; 145 break; 146 case 'v': 147 vflag++; 148 break; 149 case 'l': 150 maxrun = atoi(optarg); 151 break; 152 default: 153 usage(); 154 } 155 } 156 argc -= optind; 157 argv += optind; 158 if ((argc == 0 && !aflag) || (argc > 0 && aflag)) 159 usage(); 160 if (!gflag && !uflag) { 161 gflag++; 162 uflag++; 163 } 164 if (gflag) { 165 setgrent(); 166 while ((gr = getgrent()) != NULL) 167 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name); 168 endgrent(); 169 } 170 if (uflag) { 171 setpwent(); 172 while ((pw = getpwent()) != NULL) 173 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name); 174 endpwent(); 175 } 176 if (aflag) 177 exit(checkfstab(1, maxrun, needchk, chkquota)); 178 if (setfsent() == 0) 179 errx(1, "%s: can't open", FSTAB); 180 while ((fs = getfsent()) != NULL) { 181 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 || 182 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) && 183 (auxdata = needchk(fs)) && 184 (name = blockcheck(fs->fs_spec))) { 185 done |= 1 << argnum; 186 errs += chkquota(name, fs->fs_file, auxdata); 187 } 188 } 189 endfsent(); 190 for (i = 0; i < argc; i++) 191 if ((done & (1 << i)) == 0) 192 fprintf(stderr, "%s not found in %s\n", 193 argv[i], FSTAB); 194 exit(errs); 195 } 196 197 void 198 usage() 199 { 200 (void)fprintf(stderr, "%s\n%s\n", 201 "usage: quotacheck -a [-guv]", 202 " quotacheck [-guv] filesys ..."); 203 exit(1); 204 } 205 206 void * 207 needchk(fs) 208 register struct fstab *fs; 209 { 210 register struct quotaname *qnp; 211 char *qfnp; 212 213 if (strcmp(fs->fs_vfstype, "ufs") || 214 strcmp(fs->fs_type, FSTAB_RW)) 215 return (NULL); 216 if ((qnp = malloc(sizeof(*qnp))) == NULL) 217 errx(1, "malloc failed"); 218 qnp->flags = 0; 219 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) { 220 strcpy(qnp->grpqfname, qfnp); 221 qnp->flags |= HASGRP; 222 } 223 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) { 224 strcpy(qnp->usrqfname, qfnp); 225 qnp->flags |= HASUSR; 226 } 227 if (qnp->flags) 228 return (qnp); 229 free(qnp); 230 return (NULL); 231 } 232 233 /* 234 * Scan the specified filesystem to check quota(s) present on it. 235 */ 236 int 237 chkquota(fsname, mntpt, qnp) 238 char *fsname, *mntpt; 239 register struct quotaname *qnp; 240 { 241 register struct fileusage *fup; 242 register struct dinode *dp; 243 int cg, i, mode, errs = 0; 244 ino_t ino; 245 246 if ((fi = open(fsname, O_RDONLY, 0)) < 0) { 247 warn("%s", fsname); 248 return (1); 249 } 250 if (vflag) { 251 (void)printf("*** Checking "); 252 if (qnp->flags & HASUSR) 253 (void)printf("%s%s", qfextension[USRQUOTA], 254 (qnp->flags & HASGRP) ? " and " : ""); 255 if (qnp->flags & HASGRP) 256 (void)printf("%s", qfextension[GRPQUOTA]); 257 (void)printf(" quotas for %s (%s)\n", fsname, mntpt); 258 } 259 sync(); 260 dev_bsize = 1; 261 bread(SBOFF, (char *)&sblock, (long)SBSIZE); 262 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1); 263 maxino = sblock.fs_ncg * sblock.fs_ipg; 264 resetinodebuf(); 265 for (ino = 0, cg = 0; cg < sblock.fs_ncg; cg++) { 266 for (i = 0; i < sblock.fs_ipg; i++, ino++) { 267 if (ino < ROOTINO) 268 continue; 269 if ((dp = getnextinode(ino)) == NULL) 270 continue; 271 if ((mode = dp->di_mode & IFMT) == 0) 272 continue; 273 if (qnp->flags & HASGRP) { 274 fup = addid((u_long)dp->di_gid, GRPQUOTA, 275 (char *)0); 276 fup->fu_curinodes++; 277 if (mode == IFREG || mode == IFDIR || 278 mode == IFLNK) 279 fup->fu_curblocks += dp->di_blocks; 280 } 281 if (qnp->flags & HASUSR) { 282 fup = addid((u_long)dp->di_uid, USRQUOTA, 283 (char *)0); 284 fup->fu_curinodes++; 285 if (mode == IFREG || mode == IFDIR || 286 mode == IFLNK) 287 fup->fu_curblocks += dp->di_blocks; 288 } 289 } 290 } 291 freeinodebuf(); 292 if (qnp->flags & HASUSR) 293 errs += update(mntpt, qnp->usrqfname, USRQUOTA); 294 if (qnp->flags & HASGRP) 295 errs += update(mntpt, qnp->grpqfname, GRPQUOTA); 296 close(fi); 297 return (errs); 298 } 299 300 /* 301 * Update a specified quota file. 302 */ 303 int 304 update(fsname, quotafile, type) 305 char *fsname, *quotafile; 306 register int type; 307 { 308 register struct fileusage *fup; 309 register FILE *qfi, *qfo; 310 register u_long id, lastid; 311 register off_t offset; 312 struct dqblk dqbuf; 313 static int warned = 0; 314 static struct dqblk zerodqbuf; 315 static struct fileusage zerofileusage; 316 317 if ((qfo = fopen(quotafile, "r+")) == NULL) { 318 if (errno == ENOENT) 319 qfo = fopen(quotafile, "w+"); 320 if (qfo) { 321 warnx("creating quota file %s", quotafile); 322 #define MODE (S_IRUSR|S_IWUSR|S_IRGRP) 323 (void) fchown(fileno(qfo), getuid(), getquotagid()); 324 (void) fchmod(fileno(qfo), MODE); 325 } else { 326 warn("%s", quotafile); 327 return (1); 328 } 329 } 330 if ((qfi = fopen(quotafile, "r")) == NULL) { 331 warn("%s", quotafile); 332 (void) fclose(qfo); 333 return (1); 334 } 335 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 && 336 errno == EOPNOTSUPP && !warned && vflag) { 337 warned++; 338 (void)printf("*** Warning: %s\n", 339 "Quotas are not compiled into this kernel"); 340 } 341 for (lastid = highid[type], id = 0, offset = 0; id <= lastid; 342 id++, offset += sizeof(struct dqblk)) { 343 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0) 344 dqbuf = zerodqbuf; 345 if ((fup = lookup(id, type)) == 0) 346 fup = &zerofileusage; 347 if (dqbuf.dqb_curinodes == fup->fu_curinodes && 348 dqbuf.dqb_curblocks == fup->fu_curblocks) { 349 fup->fu_curinodes = 0; 350 fup->fu_curblocks = 0; 351 continue; 352 } 353 if (vflag) { 354 if (aflag) 355 printf("%s: ", fsname); 356 printf("%-8s fixed:", fup->fu_name); 357 if (dqbuf.dqb_curinodes != fup->fu_curinodes) 358 (void)printf("\tinodes %lu -> %lu", 359 (u_long)dqbuf.dqb_curinodes, 360 (u_long)fup->fu_curinodes); 361 if (dqbuf.dqb_curblocks != fup->fu_curblocks) 362 (void)printf("\tblocks %lu -> %lu", 363 (u_long)dqbuf.dqb_curblocks, 364 (u_long)fup->fu_curblocks); 365 (void)printf("\n"); 366 } 367 /* 368 * Reset time limit if have a soft limit and were 369 * previously under it, but are now over it. 370 */ 371 if (dqbuf.dqb_bsoftlimit && 372 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit && 373 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit) 374 dqbuf.dqb_btime = 0; 375 if (dqbuf.dqb_isoftlimit && 376 dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit && 377 fup->fu_curblocks >= dqbuf.dqb_isoftlimit) 378 dqbuf.dqb_itime = 0; 379 dqbuf.dqb_curinodes = fup->fu_curinodes; 380 dqbuf.dqb_curblocks = fup->fu_curblocks; 381 if (fseek(qfo, (long)offset, SEEK_SET) < 0) { 382 warn("%s: seek failed", quotafile); 383 return(1); 384 } 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)) != NULL) 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, '=')) != NULL) 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)) != NULL) 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 errx(1, "calloc failed"); 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, "%lu", id); 518 if (vflag) 519 printf("unknown %cid: %lu\n", 520 type == USRQUOTA ? 'u' : 'g', id); 521 } 522 return (fup); 523 } 524 525 /* 526 * Special purpose version of ginode used to optimize pass 527 * over all the inodes in numerical order. 528 */ 529 ino_t nextino, lastinum; 530 long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 531 struct dinode *inodebuf; 532 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 533 534 struct dinode * 535 getnextinode(inumber) 536 ino_t inumber; 537 { 538 long size; 539 daddr_t dblk; 540 static struct dinode *dp; 541 542 if (inumber != nextino++ || inumber > maxino) 543 errx(1, "bad inode number %d to nextinode", inumber); 544 if (inumber >= lastinum) { 545 readcnt++; 546 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 547 if (readcnt % readpercg == 0) { 548 size = partialsize; 549 lastinum += partialcnt; 550 } else { 551 size = inobufsize; 552 lastinum += fullcnt; 553 } 554 bread(dblk, (char *)inodebuf, size); 555 dp = inodebuf; 556 } 557 return (dp++); 558 } 559 560 /* 561 * Prepare to scan a set of inodes. 562 */ 563 void 564 resetinodebuf() 565 { 566 567 nextino = 0; 568 lastinum = 0; 569 readcnt = 0; 570 inobufsize = blkroundup(&sblock, INOBUFSIZE); 571 fullcnt = inobufsize / sizeof(struct dinode); 572 readpercg = sblock.fs_ipg / fullcnt; 573 partialcnt = sblock.fs_ipg % fullcnt; 574 partialsize = partialcnt * sizeof(struct dinode); 575 if (partialcnt != 0) { 576 readpercg++; 577 } else { 578 partialcnt = fullcnt; 579 partialsize = inobufsize; 580 } 581 if (inodebuf == NULL && 582 (inodebuf = malloc((u_int)inobufsize)) == NULL) 583 errx(1, "malloc failed"); 584 while (nextino < ROOTINO) 585 getnextinode(nextino); 586 } 587 588 /* 589 * Free up data structures used to scan inodes. 590 */ 591 void 592 freeinodebuf() 593 { 594 595 if (inodebuf != NULL) 596 free(inodebuf); 597 inodebuf = NULL; 598 } 599 600 /* 601 * Read specified disk blocks. 602 */ 603 void 604 bread(bno, buf, cnt) 605 daddr_t bno; 606 char *buf; 607 long cnt; 608 { 609 610 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 611 read(fi, buf, cnt) != cnt) 612 errx(1, "block %ld", (long)bno); 613 } 614