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