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: #if !defined(lint) && defined(DOSCCS) 38: static char copyright[] = 39: "@(#) Copyright (c) 1980, 1990, 1993\n\ 40: The Regents of the University of California. All rights reserved.\n"; 41: 42: static char sccsid[] = "@(#)quotacheck.c 8.3.1 (2.11BSD) 1996/1/23"; 43: #endif /* not lint */ 44: 45: /* 46: * Fix up / report on disk quotas & usage 47: */ 48: #include <sys/param.h> 49: #include <sys/stat.h> 50: 51: #include <sys/inode.h> 52: #include <sys/quota.h> 53: #include <sys/fs.h> 54: 55: #include <fcntl.h> 56: #include <fstab.h> 57: #include <pwd.h> 58: #include <grp.h> 59: #include <errno.h> 60: #include <unistd.h> 61: #include <stdio.h> 62: #include <stdlib.h> 63: #include <string.h> 64: 65: char *qfname = QUOTAFILENAME; 66: char *quotagroup = QUOTAGROUP; 67: 68: union { 69: struct fs sblk; 70: char dummy[MAXBSIZE]; 71: } un; 72: #define sblock un.sblk 73: ino_t maxino; 74: 75: struct quotaname { 76: char flags; 77: char usrqfname[MAXPATHLEN + 1]; 78: }; 79: #define HASUSR 1 80: 81: struct fileusage { 82: struct fileusage *fu_next; 83: ino_t fu_curinodes; 84: u_long fu_curblocks; 85: uid_t fu_id; 86: char fu_name[1]; 87: /* actually bigger */ 88: }; 89: #define FUHASH 256 /* must be power of two */ 90: struct fileusage *fuhead[FUHASH]; 91: 92: int aflag; /* all file systems */ 93: int vflag; /* verbose */ 94: int fi; /* open disk file descriptor */ 95: uid_t highid; /* highest addid()'ed identifier */ 96: 97: struct fileusage *addid(); 98: char *blockcheck(); 99: void bread(); 100: int chkquota(); 101: void freeinodebuf(); 102: struct dinode *getnextinode(); 103: gid_t getquotagid(); 104: int hasquota(); 105: struct fileusage *lookup(); 106: void *needchk(); 107: int oneof(); 108: void resetinodebuf(); 109: int update(); 110: void usage(); 111: 112: int 113: main(argc, argv) 114: int argc; 115: char *argv[]; 116: { 117: register struct fstab *fs; 118: register struct passwd *pw; 119: struct quotaname *auxdata; 120: int i, argnum, maxrun, errs, ch; 121: long done = 0; 122: char *name; 123: 124: errs = maxrun = 0; 125: while ((ch = getopt(argc, argv, "avl:")) != EOF) { 126: switch(ch) { 127: case 'a': 128: aflag++; 129: break; 130: case 'v': 131: vflag++; 132: break; 133: case 'l': 134: maxrun = atoi(optarg); 135: break; 136: default: 137: usage(); 138: } 139: } 140: argc -= optind; 141: argv += optind; 142: if ((argc == 0 && !aflag) || (argc > 0 && aflag)) 143: usage(); 144: 145: setpwent(); 146: while ((pw = getpwent()) != 0) 147: (void) addid(pw->pw_uid, pw->pw_name); 148: endpwent(); 149: 150: if (aflag) 151: exit(checkfstab(1, maxrun, needchk, chkquota)); 152: if (setfsent() == 0) 153: err(1, "can't open %s", FSTAB); 154: while ((fs = getfsent()) != NULL) { 155: if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 || 156: (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) && 157: (auxdata = (struct quotaname *)needchk(fs)) && 158: (name = blockcheck(fs->fs_spec))) { 159: done |= 1 << argnum; 160: errs += chkquota(name, fs->fs_file, auxdata); 161: } 162: } 163: endfsent(); 164: for (i = 0; i < argc; i++) 165: if ((done & (1 << i)) == 0) 166: warn("%s not found in %s", argv[i], FSTAB); 167: exit(errs); 168: } 169: 170: void 171: usage() 172: { 173: (void)err(1, "usage:\t%s\n\t%s\n", 174: "quotacheck -a [-v] [-l num]", 175: "quotacheck [-v] filesys ..."); 176: /* NOTREACHED */ 177: } 178: 179: void * 180: needchk(fs) 181: register struct fstab *fs; 182: { 183: register struct quotaname *qnp; 184: char *qfnp; 185: 186: if (strcmp(fs->fs_vfstype, "ufs") || 187: (strcmp(fs->fs_type, FSTAB_RW) && strcmp(fs->fs_type, FSTAB_RQ))) 188: return ((void *)NULL); 189: if ((qnp = (struct quotaname *)malloc(sizeof(*qnp))) == NULL) 190: nomem(); 191: qnp->flags = 0; 192: if (hasquota(fs, &qfnp)) { 193: strcpy(qnp->usrqfname, qfnp); 194: qnp->flags |= HASUSR; 195: } 196: if (qnp->flags) 197: return ((void *)qnp); 198: free(qnp); 199: return ((void *)NULL); 200: } 201: 202: /* 203: * Scan the specified filesystem to check quota(s) present on it. 204: */ 205: int 206: chkquota(fsname, mntpt, qnp) 207: char *fsname, *mntpt; 208: register struct quotaname *qnp; 209: { 210: register struct fileusage *fup; 211: register struct dinode *dp; 212: int mode, errs = 0; 213: ino_t ino; 214: 215: if ((fi = open(fsname, O_RDONLY, 0)) < 0) { 216: perror(fsname); 217: return (1); 218: } 219: if (vflag) 220: (void)printf("*** Checking quotas for %s (%s)\n", 221: fsname, mntpt); 222: sync(); 223: bread(SBLOCK, (char *)&sblock, SBSIZE); 224: maxino = (sblock.fs_isize - 2) * INOPB; 225: resetinodebuf(); 226: for (ino = ROOTINO; ino < maxino; ino++) { 227: if ((dp = getnextinode(ino)) == NULL) 228: continue; 229: if ((mode = dp->di_mode & IFMT) == 0) 230: continue; 231: if (qnp->flags & HASUSR) { 232: fup = addid(dp->di_uid, (char *)0); 233: fup->fu_curinodes++; 234: if (mode == IFREG || mode == IFDIR || mode == IFLNK) 235: fup->fu_curblocks += dp->di_size; 236: } 237: } 238: freeinodebuf(); 239: if (qnp->flags & HASUSR) 240: errs += update(fsname, mntpt, qnp->usrqfname); 241: close(fi); 242: return (errs); 243: } 244: 245: /* 246: * Update a specified quota file. 247: */ 248: int 249: update(fsdev, fsname, quotafile) 250: char *fsdev, *fsname, *quotafile; 251: { 252: register struct fileusage *fup; 253: register FILE *qfi, *qfo; 254: uid_t id, lastid; 255: struct dqblk dqbuf; 256: struct stat statb; 257: struct dqusage dqu; 258: dev_t quotadev; 259: static int warned = 0; 260: static struct dqblk zerodqbuf; 261: static struct fileusage zerofileusage; 262: 263: if ((qfo = fopen(quotafile, "r+")) == NULL) { 264: if (errno == ENOENT) 265: qfo = fopen(quotafile, "w+"); 266: if (qfo) { 267: (void) warn("creating quota file %s", quotafile); 268: (void) fchown(fileno(qfo), getuid(), getquotagid()); 269: (void) fchmod(fileno(qfo), 0640); 270: } else { 271: (void) warn("%s: %s", quotafile, strerror(errno)); 272: return (1); 273: } 274: } 275: if ((qfi = fopen(quotafile, "r")) == NULL) { 276: (void) warn("%s: %s", quotafile, strerror(errno)); 277: (void) fclose(qfo); 278: return (1); 279: } 280: /* 281: * We check that the quota file resides in the filesystem being checked. This 282: * restriction is imposed by the kernel for now. 283: */ 284: fstat(fileno(qfi), &statb); 285: quotadev = statb.st_dev; 286: if (stat(unrawname(fsdev), &statb) < 0) { 287: warn("Can't stat %s", fsname); 288: fclose(qfi); 289: fclose(qfo); 290: return(1); 291: } 292: if (quotadev != statb.st_rdev) { 293: warn("%s dev (0x%x) mismatch %s dev (0x%x)", quotafile, 294: quotadev, fsname, statb.st_rdev); 295: fclose(qfi); 296: fclose(qfo); 297: return(1); 298: } 299: if (quota(Q_SYNC, 0, quotadev, 0) < 0 && 300: errno == EOPNOTSUPP && !warned && vflag) { 301: warned++; 302: (void)printf("*** Warning: %s\n", 303: "Quotas are not compiled into this kernel"); 304: } 305: for (lastid = highid, id = 0; id <= lastid; id++) { 306: if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0) 307: dqbuf = zerodqbuf; 308: if ((fup = lookup(id)) == 0) 309: fup = &zerofileusage; 310: if (dqbuf.dqb_curinodes == fup->fu_curinodes && 311: dqbuf.dqb_curblocks == fup->fu_curblocks) { 312: fup->fu_curinodes = 0; 313: fup->fu_curblocks = 0; 314: fseek(qfo, (long)sizeof(struct dqblk), 1); 315: continue; 316: } 317: if (vflag) { 318: if (aflag) 319: printf("%s: ", fsname); 320: printf("%-8s fixed:", fup->fu_name); 321: if (dqbuf.dqb_curinodes != fup->fu_curinodes) 322: (void)printf("\tinodes %u -> %u", 323: dqbuf.dqb_curinodes, fup->fu_curinodes); 324: if (dqbuf.dqb_curblocks != fup->fu_curblocks) 325: (void)printf("\tbytes %ld -> %ld", 326: dqbuf.dqb_curblocks, fup->fu_curblocks); 327: (void)printf("\n"); 328: } 329: dqbuf.dqb_curinodes = fup->fu_curinodes; 330: dqbuf.dqb_curblocks = fup->fu_curblocks; 331: fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo); 332: /* 333: * Need to convert from bytes to blocks for system call interface. 334: */ 335: dqu.du_curblocks = btodb(fup->fu_curblocks); 336: dqu.du_curinodes = fup->fu_curinodes; 337: (void) quota(Q_SETDUSE, id, quotadev, &dqu); 338: fup->fu_curinodes = 0; 339: fup->fu_curblocks = 0; 340: } 341: fclose(qfi); 342: fflush(qfo); 343: ftruncate(fileno(qfo), (off_t)(highid + 1) * sizeof(struct dqblk)); 344: fclose(qfo); 345: return (0); 346: } 347: 348: /* 349: * Check to see if target appears in list of size cnt. 350: */ 351: int 352: oneof(target, list, cnt) 353: register char *target, *list[]; 354: int cnt; 355: { 356: register int i; 357: 358: for (i = 0; i < cnt; i++) 359: if (strcmp(target, list[i]) == 0) 360: return (i); 361: return (-1); 362: } 363: 364: /* 365: * Determine the group identifier for quota files. 366: */ 367: gid_t 368: getquotagid() 369: { 370: struct group *gr; 371: 372: if (gr = getgrnam(quotagroup)) 373: return (gr->gr_gid); 374: return (-1); 375: } 376: 377: /* 378: * Check to see if a particular quota is to be enabled. 379: */ 380: hasquota(fs, qfnamep) 381: register struct fstab *fs; 382: char **qfnamep; 383: { 384: register char *opt; 385: char *cp; 386: static char initname, usrname[100]; 387: static char buf[BUFSIZ]; 388: 389: if (!initname) { 390: strcpy(usrname, qfname); 391: initname = 1; 392: } 393: strcpy(buf, fs->fs_mntops); 394: for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 395: if (cp = index(opt, '=')) 396: *cp++ = '\0'; 397: if (strcmp(opt, usrname) == 0) 398: break; 399: if (strcmp(opt, FSTAB_RQ) == 0) /* XXX compatibility */ 400: break; 401: } 402: if (!opt) 403: return (0); 404: if (cp) { 405: *qfnamep = cp; 406: return (1); 407: } 408: (void) sprintf(buf, "%s/%s", fs->fs_file, qfname); 409: *qfnamep = buf; 410: return (1); 411: } 412: 413: /* 414: * Routines to manage the file usage table. 415: * 416: * Lookup an id. 417: */ 418: struct fileusage * 419: lookup(id) 420: uid_t id; 421: { 422: register struct fileusage *fup; 423: 424: for (fup = fuhead[id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 425: if (fup->fu_id == id) 426: return (fup); 427: return (NULL); 428: } 429: 430: /* 431: * Add a new file usage id if it does not already exist. 432: */ 433: struct fileusage * 434: addid(id, name) 435: uid_t id; 436: char *name; 437: { 438: register struct fileusage *fup, **fhp; 439: int len; 440: 441: if (fup = lookup(id)) 442: return (fup); 443: if (name) 444: len = strlen(name); 445: else 446: len = 10; 447: if ((fup = (struct fileusage *)calloc(1, sizeof(*fup) + len)) == NULL) 448: err("%s", strerror(errno)); 449: fhp = &fuhead[id & (FUHASH - 1)]; 450: fup->fu_next = *fhp; 451: *fhp = fup; 452: fup->fu_id = id; 453: if (id > highid) 454: highid = id; 455: if (name) 456: bcopy(name, fup->fu_name, len + 1); 457: else 458: (void)sprintf(fup->fu_name, "%u", id); 459: return (fup); 460: } 461: 462: /* 463: * Special purpose version of ginode used to optimize pass 464: * over all the inodes in numerical order. 465: */ 466: ino_t nextino; 467: long lastinum; 468: int inobufsize; 469: struct dinode *inodebuf; 470: #define INOBUFSIZE 128 /* number of inodes to read at a time */ 471: 472: struct dinode * 473: getnextinode(inumber) 474: ino_t inumber; 475: { 476: daddr_t dblk; 477: static struct dinode *dp; 478: 479: if (inumber != nextino++ || inumber >= maxino) 480: err(1, "bad inode number %d to nextinode", inumber); 481: if (inumber > lastinum) { 482: dblk = itod(inumber); 483: lastinum += INOBUFSIZE; 484: bread(dblk, (char *)inodebuf, inobufsize); 485: dp = inodebuf; 486: } 487: return (dp++); 488: } 489: 490: /* 491: * Prepare to scan a set of inodes. 492: */ 493: void 494: resetinodebuf() 495: { 496: 497: nextino = 1; 498: lastinum = 0; 499: inobufsize = INOBUFSIZE * sizeof (struct dinode); 500: if (inodebuf == NULL && 501: (inodebuf = (struct dinode *)malloc(inobufsize)) == NULL) 502: nomem(); 503: while (nextino < ROOTINO) 504: getnextinode(nextino); 505: } 506: 507: /* 508: * Free up data structures used to scan inodes. 509: */ 510: void 511: freeinodebuf() 512: { 513: 514: if (inodebuf != NULL) 515: free(inodebuf); 516: inodebuf = NULL; 517: } 518: 519: /* 520: * Read specified disk blocks. 521: */ 522: void 523: bread(bno, buf, cnt) 524: daddr_t bno; 525: char *buf; 526: int cnt; 527: { 528: 529: if (lseek(fi, (off_t)bno * DEV_BSIZE, 0) == -1 || 530: read(fi, buf, cnt) != cnt) 531: err(1, "block %ld", bno); 532: }