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