1 /* $OpenBSD: quot.c,v 1.20 2009/10/27 23:59:54 deraadt Exp $ */ 2 3 /* 4 * Copyright (C) 1991, 1994 Wolfgang Solfrank. 5 * Copyright (C) 1991, 1994 TooLs GmbH. 6 * All rights reserved. 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 TooLs GmbH. 19 * 4. The name of TooLs GmbH may not be used to endorse or promote products 20 * derived from this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 28 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <sys/param.h> 35 #include <sys/mount.h> 36 #include <sys/time.h> 37 #include <ufs/ufs/dinode.h> 38 #include <ufs/ffs/fs.h> 39 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <err.h> 44 #include <errno.h> 45 #include <fcntl.h> 46 #include <paths.h> 47 #include <pwd.h> 48 #include <unistd.h> 49 50 /* some flags of what to do: */ 51 static char estimate; 52 static char count; 53 static char unused; 54 static void (*func)(int, struct fs *, char *); 55 static int cmpusers(const void *, const void *); 56 static long blocksize; 57 static char *header; 58 static int headerlen; 59 60 /* 61 * Original BSD quot doesn't round to number of frags/blocks, 62 * doesn't account for indirection blocks and gets it totally 63 * wrong if the size is a multiple of the blocksize. 64 * The new code always counts the number of DEV_BSIZE byte blocks 65 * instead of the number of kilobytes and converts them to 66 * KByte when done (on request). 67 */ 68 #ifdef COMPAT 69 #define SIZE(n) (n) 70 #else 71 #define SIZE(n) (howmany(((off_t)(n)) * DEV_BSIZE, blocksize)) 72 #endif 73 74 #define INOCNT(fs) ((fs)->fs_ipg) 75 #define INOSZ(fs) (((fs)->fs_magic == FS_UFS1_MAGIC ? \ 76 sizeof(struct ufs1_dinode) : \ 77 sizeof(struct ufs2_dinode)) * INOCNT(fs)) 78 79 union dinode { 80 struct ufs1_dinode dp1; 81 struct ufs2_dinode dp2; 82 }; 83 #define DIP(fs, dp, field) \ 84 (((fs)->fs_magic == FS_UFS1_MAGIC) ? \ 85 (dp)->dp1.field : (dp)->dp2.field) 86 87 static union dinode * 88 get_inode(int fd, struct fs *super, ino_t ino) 89 { 90 static caddr_t ipbuf; 91 static struct cg *cgp; 92 static ino_t last; 93 static int cg; 94 struct ufs2_dinode *di2; 95 96 if (fd < 0) { /* flush cache */ 97 if (ipbuf) { 98 free(ipbuf); 99 ipbuf = NULL; 100 if (super != NULL && super->fs_magic == FS_UFS2_MAGIC) { 101 free(cgp); 102 cgp = NULL; 103 } 104 } 105 return 0; 106 } 107 108 if (!ipbuf || ino < last || ino >= last + INOCNT(super)) { 109 if (super->fs_magic == FS_UFS2_MAGIC && 110 (!cgp || cg != ino_to_cg(super, ino))) { 111 cg = ino_to_cg(super, ino); 112 if (!cgp && !(cgp = malloc(super->fs_cgsize))) 113 errx(1, "allocate cg"); 114 if (pread(fd, cgp, super->fs_cgsize, 115 (off_t)cgtod(super, cg) << super->fs_fshift) 116 != super->fs_cgsize) 117 if (read(fd, cgp, super->fs_cgsize) != super->fs_cgsize) 118 err(1, "read cg"); 119 if (!cg_chkmagic(cgp)) 120 errx(1, "cg has bad magic"); 121 } 122 if (!ipbuf && !(ipbuf = malloc(INOSZ(super)))) 123 err(1, "allocate inodes"); 124 last = (ino / INOCNT(super)) * INOCNT(super); 125 if (lseek(fd, (off_t)ino_to_fsba(super, last) 126 << super->fs_fshift, SEEK_SET) < 0 || 127 read(fd, ipbuf, INOSZ(super)) != INOSZ(super)) { 128 err(1, "read inodes"); 129 } 130 } 131 132 if (super->fs_magic == FS_UFS1_MAGIC) 133 return ((union dinode *) 134 &((struct ufs1_dinode *)ipbuf)[ino % INOCNT(super)]); 135 di2 = &((struct ufs2_dinode *)ipbuf)[ino % INOCNT(super)]; 136 /* If the inode is unused, it might be unallocated too, so zero it. */ 137 if (isclr(cg_inosused(cgp), ino % super->fs_ipg)) 138 memset(di2, 0, sizeof(*di2)); 139 return ((union dinode *)di2); 140 } 141 142 #ifdef COMPAT 143 #define actualblocks(fs, ip) (DIP(fs, dp, di_blocks) / 2) 144 #else 145 #define actualblocks(fs, ip) DIP(fs, dp, di_blocks) 146 #endif 147 148 static int 149 virtualblocks(struct fs *super, union dinode *dp) 150 { 151 off_t nblk, sz; 152 153 sz = DIP(super, dp, di_size); 154 #ifdef COMPAT 155 if (lblkno(super, sz) >= NDADDR) { 156 nblk = blkroundup(super, sz); 157 if (sz == nblk) 158 nblk += super->fs_bsize; 159 } 160 161 return sz / 1024; 162 #else /* COMPAT */ 163 164 if (lblkno(super, sz) >= NDADDR) { 165 nblk = blkroundup(super, sz); 166 sz = lblkno(super, nblk); 167 sz = howmany(sz - NDADDR, NINDIR(super)); 168 while (sz > 0) { 169 nblk += sz * super->fs_bsize; 170 /* One block on this level is in the inode itself */ 171 sz = howmany(sz - 1, NINDIR(super)); 172 } 173 } else 174 nblk = fragroundup(super, sz); 175 176 return nblk / DEV_BSIZE; 177 #endif /* COMPAT */ 178 } 179 180 static int 181 isfree(struct fs *super, union dinode *dp) 182 { 183 #ifdef COMPAT 184 return (DIP(super, dp, di_mode) & IFMT) == 0; 185 #else /* COMPAT */ 186 switch (DIP(super, dp, di_mode) & IFMT) { 187 case IFIFO: 188 case IFLNK: /* should check FASTSYMLINK? */ 189 case IFDIR: 190 case IFREG: 191 return 0; 192 case IFCHR: 193 case IFBLK: 194 case IFSOCK: 195 case 0: 196 return 1; 197 default: 198 errx(1, "unknown IFMT 0%o", DIP(super, dp, di_mode) & IFMT); 199 } 200 #endif 201 } 202 203 static struct user { 204 uid_t uid; 205 char *name; 206 daddr64_t space; 207 long count; 208 daddr64_t spc30; 209 daddr64_t spc60; 210 daddr64_t spc90; 211 } *users; 212 static int nusers; 213 214 static void 215 inituser(void) 216 { 217 int i; 218 struct user *usr; 219 220 if (!nusers) { 221 nusers = 8; 222 if (!(users = 223 (struct user *)calloc(nusers, sizeof(struct user)))) { 224 err(1, "allocate users"); 225 } 226 } else { 227 for (usr = users, i = nusers; --i >= 0; usr++) { 228 usr->space = usr->spc30 = usr->spc60 = usr->spc90 = 0; 229 usr->count = 0; 230 } 231 } 232 } 233 234 static void 235 usrrehash(void) 236 { 237 int i; 238 struct user *usr, *usrn; 239 struct user *svusr; 240 241 svusr = users; 242 nusers <<= 1; 243 if (!(users = (struct user *)calloc(nusers, sizeof(struct user)))) 244 err(1, "allocate users"); 245 for (usr = svusr, i = nusers >> 1; --i >= 0; usr++) { 246 for (usrn = users + (usr->uid&(nusers - 1)); 247 usrn->name; 248 usrn--) { 249 if (usrn <= users) 250 usrn = users + nusers; 251 } 252 *usrn = *usr; 253 } 254 } 255 256 static struct user * 257 user(uid_t uid) 258 { 259 int i; 260 struct passwd *pwd; 261 struct user *usr; 262 263 while (1) { 264 for (usr = users + (uid&(nusers - 1)), i = nusers; 265 --i >= 0; 266 usr--) { 267 if (!usr->name) { 268 usr->uid = uid; 269 270 if (!(pwd = getpwuid(uid))) 271 asprintf(&usr->name, "#%u", uid); 272 else 273 usr->name = strdup(pwd->pw_name); 274 if (!usr->name) 275 err(1, "allocate users"); 276 return usr; 277 } else if (usr->uid == uid) 278 return usr; 279 280 if (usr <= users) 281 usr = users + nusers; 282 } 283 usrrehash(); 284 } 285 } 286 287 static int 288 cmpusers(const void *v1, const void *v2) 289 { 290 const struct user *u1 = v1, *u2 = v2; 291 292 return u2->space - u1->space; 293 } 294 295 #define sortusers(users) (qsort((users), nusers, sizeof(struct user), \ 296 cmpusers)) 297 298 static void 299 uses(uid_t uid, daddr64_t blks, time_t act) 300 { 301 static time_t today; 302 struct user *usr; 303 304 if (!today) 305 time(&today); 306 307 usr = user(uid); 308 usr->count++; 309 usr->space += blks; 310 311 if (today - act > 90L * 24L * 60L * 60L) 312 usr->spc90 += blks; 313 if (today - act > 60L * 24L * 60L * 60L) 314 usr->spc60 += blks; 315 if (today - act > 30L * 24L * 60L * 60L) 316 usr->spc30 += blks; 317 } 318 319 #ifdef COMPAT 320 #define FSZCNT 500 321 #else 322 #define FSZCNT 512 323 #endif 324 struct fsizes { 325 struct fsizes *fsz_next; 326 daddr64_t fsz_first, fsz_last; 327 ino_t fsz_count[FSZCNT]; 328 daddr64_t fsz_sz[FSZCNT]; 329 } *fsizes; 330 331 static void 332 initfsizes(void) 333 { 334 struct fsizes *fp; 335 int i; 336 337 for (fp = fsizes; fp; fp = fp->fsz_next) { 338 for (i = FSZCNT; --i >= 0;) { 339 fp->fsz_count[i] = 0; 340 fp->fsz_sz[i] = 0; 341 } 342 } 343 } 344 345 static void 346 dofsizes(int fd, struct fs *super, char *name) 347 { 348 ino_t inode, maxino; 349 union dinode *dp; 350 daddr64_t sz, ksz; 351 struct fsizes *fp, **fsp; 352 int i; 353 354 maxino = super->fs_ncg * super->fs_ipg - 1; 355 #ifdef COMPAT 356 if (!(fsizes = (struct fsizes *)malloc(sizeof(struct fsizes)))) 357 err(1, "alloc fsize structure"); 358 #endif /* COMPAT */ 359 for (inode = 0; inode < maxino; inode++) { 360 errno = 0; 361 if ((dp = get_inode(fd, super, inode)) 362 #ifdef COMPAT 363 && ((DIP(super, dp, di_mode) & IFMT) == IFREG 364 || (DIP(super, dp, di_mode) & IFMT) == IFDIR) 365 #else /* COMPAT */ 366 && !isfree(super, dp) 367 #endif /* COMPAT */ 368 ) { 369 sz = estimate ? virtualblocks(super, dp) : 370 actualblocks(super, dp); 371 #ifdef COMPAT 372 if (sz >= FSZCNT) { 373 fsizes->fsz_count[FSZCNT-1]++; 374 fsizes->fsz_sz[FSZCNT-1] += sz; 375 } else { 376 fsizes->fsz_count[sz]++; 377 fsizes->fsz_sz[sz] += sz; 378 } 379 #else /* COMPAT */ 380 ksz = SIZE(sz); 381 for (fsp = &fsizes; (fp = *fsp); fsp = &fp->fsz_next) { 382 if (ksz < fp->fsz_last) 383 break; 384 } 385 if (!fp || ksz < fp->fsz_first) { 386 if (!(fp = (struct fsizes *) 387 malloc(sizeof(struct fsizes)))) { 388 err(1, "alloc fsize structure"); 389 } 390 fp->fsz_next = *fsp; 391 *fsp = fp; 392 fp->fsz_first = (ksz / FSZCNT) * FSZCNT; 393 fp->fsz_last = fp->fsz_first + FSZCNT; 394 for (i = FSZCNT; --i >= 0;) { 395 fp->fsz_count[i] = 0; 396 fp->fsz_sz[i] = 0; 397 } 398 } 399 fp->fsz_count[ksz % FSZCNT]++; 400 fp->fsz_sz[ksz % FSZCNT] += sz; 401 #endif /* COMPAT */ 402 } else if (errno) 403 err(1, "%s", name); 404 } 405 sz = 0; 406 for (fp = fsizes; fp; fp = fp->fsz_next) { 407 for (i = 0; i < FSZCNT; i++) { 408 if (fp->fsz_count[i]) 409 printf("%d\t%d\t%lld\n", 410 fp->fsz_first + i, fp->fsz_count[i], 411 SIZE(sz += fp->fsz_sz[i])); 412 } 413 } 414 } 415 416 static void 417 douser(int fd, struct fs *super, char *name) 418 { 419 ino_t inode, maxino; 420 struct user *usr, *usrs; 421 union dinode *dp; 422 int n; 423 424 maxino = super->fs_ncg * super->fs_ipg - 1; 425 for (inode = 0; inode < maxino; inode++) { 426 errno = 0; 427 if ((dp = get_inode(fd,super,inode)) 428 && !isfree(super, dp)) 429 uses(DIP(super, dp, di_uid), 430 estimate ? virtualblocks(super, dp) : 431 actualblocks(super, dp), 432 DIP(super, dp, di_atime)); 433 else if (errno) 434 err(1, "%s", name); 435 } 436 if (!(usrs = (struct user *)calloc(nusers, sizeof(struct user)))) 437 err(1, "allocate users"); 438 memcpy(usrs, users, nusers * sizeof(struct user)); 439 sortusers(usrs); 440 for (usr = usrs, n = nusers; --n >= 0 && usr->count; usr++) { 441 printf("%14lld", SIZE(usr->space)); 442 if (count) 443 printf("\t%5ld", usr->count); 444 printf("\t%-8s", usr->name); 445 if (unused) 446 printf("\t%14lld\t%14lld\t%14lld", 447 SIZE(usr->spc30), 448 SIZE(usr->spc60), 449 SIZE(usr->spc90)); 450 printf("\n"); 451 } 452 free(usrs); 453 } 454 455 static void 456 donames(int fd, struct fs *super, char *name) 457 { 458 int c; 459 ino_t inode, inode1; 460 ino_t maxino; 461 union dinode *dp; 462 463 maxino = super->fs_ncg * super->fs_ipg - 1; 464 /* first skip the name of the filesystem */ 465 while ((c = getchar()) != EOF && (c < '0' || c > '9')) 466 while ((c = getchar()) != EOF && c != '\n'); 467 ungetc(c, stdin); 468 inode1 = -1; 469 while (scanf("%d", &inode) == 1) { 470 if (inode < 0 || inode > maxino) { 471 #ifndef COMPAT 472 fprintf(stderr, "invalid inode %d\n", inode); 473 #endif 474 return; 475 } 476 #ifdef COMPAT 477 if (inode < inode1) 478 continue; 479 #endif 480 errno = 0; 481 if ((dp = get_inode(fd, super, inode)) && !isfree(super, dp)) { 482 printf("%s\t", user(DIP(super, dp, di_uid))->name); 483 /* now skip whitespace */ 484 while ((c = getchar()) == ' ' || c == '\t'); 485 /* and print out the remainder of the input line */ 486 while (c != EOF && c != '\n') { 487 putchar(c); 488 c = getchar(); 489 } 490 putchar('\n'); 491 inode1 = inode; 492 } else { 493 if (errno) 494 err(1, "%s", name); 495 /* skip this line */ 496 while ((c = getchar()) != EOF && c != '\n') 497 ; 498 } 499 if (c == EOF) 500 break; 501 } 502 } 503 504 static void 505 usage(void) 506 { 507 #ifdef COMPAT 508 fprintf(stderr, "usage: quot [-nfcvha] [filesystem ...]\n"); 509 #else /* COMPAT */ 510 fprintf(stderr, "usage: quot [-acfhknv] [filesystem ...]\n"); 511 #endif /* COMPAT */ 512 exit(1); 513 } 514 515 /* 516 * Possible superblock locations ordered from most to least likely. 517 */ 518 static int sblock_try[] = SBLOCKSEARCH; 519 static char superblock[SBLOCKSIZE]; 520 521 #define max(a,b) MAX((a),(b)) 522 /* 523 * Sanity checks for old file systems. 524 * Stolen from <sys/lib/libsa/ufs.c> 525 */ 526 static void 527 ffs_oldfscompat(struct fs *fs) 528 { 529 int i; 530 531 fs->fs_npsect = max(fs->fs_npsect, fs->fs_nsect); /* XXX */ 532 fs->fs_interleave = max(fs->fs_interleave, 1); /* XXX */ 533 if (fs->fs_postblformat == FS_42POSTBLFMT) /* XXX */ 534 fs->fs_nrpos = 8; /* XXX */ 535 if (fs->fs_inodefmt < FS_44INODEFMT) { /* XXX */ 536 quad_t sizepb = fs->fs_bsize; /* XXX */ 537 /* XXX */ 538 fs->fs_maxfilesize = fs->fs_bsize * NDADDR - 1; /* XXX */ 539 for (i = 0; i < NIADDR; i++) { /* XXX */ 540 sizepb *= NINDIR(fs); /* XXX */ 541 fs->fs_maxfilesize += sizepb; /* XXX */ 542 } /* XXX */ 543 fs->fs_qbmask = ~fs->fs_bmask; /* XXX */ 544 fs->fs_qfmask = ~fs->fs_fmask; /* XXX */ 545 } /* XXX */ 546 } 547 548 void 549 quot(char *name, char *mp) 550 { 551 int i, fd; 552 struct fs *fs; 553 554 get_inode(-1, NULL, 0); /* flush cache */ 555 inituser(); 556 initfsizes(); 557 /* 558 * XXX this is completely broken. Of course you can't read a 559 * directory, well, not anymore. How to fix this, though... 560 */ 561 if ((fd = open(name, 0)) < 0) { 562 warn("%s", name); 563 return; 564 } 565 for (i = 0; sblock_try[i] != -1; i++) { 566 if (lseek(fd, sblock_try[i], 0) != sblock_try[i]) { 567 close(fd); 568 return; 569 } 570 if (read(fd, superblock, SBLOCKSIZE) != SBLOCKSIZE) { 571 close(fd); 572 return; 573 } 574 fs = (struct fs *)superblock; 575 if ((fs->fs_magic == FS_UFS1_MAGIC || 576 (fs->fs_magic == FS_UFS2_MAGIC && 577 fs->fs_sblockloc == sblock_try[i])) && 578 fs->fs_bsize <= MAXBSIZE && 579 fs->fs_bsize >= sizeof(struct fs)) 580 break; 581 } 582 if (sblock_try[i] == -1) { 583 warnx("%s: not a BSD filesystem", name); 584 close(fd); 585 return; 586 } 587 ffs_oldfscompat(fs); 588 printf("%s:", name); 589 if (mp) 590 printf(" (%s)", mp); 591 putchar('\n'); 592 (*func)(fd, fs, name); 593 close(fd); 594 } 595 596 int 597 main(int argc, char *argv[]) 598 { 599 int cnt, all, i; 600 char dev[MNAMELEN], *nm, *mountpoint, *cp; 601 struct statfs *mp; 602 603 all = 0; 604 func = douser; 605 #ifndef COMPAT 606 header = getbsize(&headerlen, &blocksize); 607 #endif 608 while (--argc > 0 && **++argv == '-') { 609 while (*++*argv) { 610 switch (**argv) { 611 case 'n': 612 func = donames; 613 break; 614 case 'c': 615 func = dofsizes; 616 break; 617 case 'a': 618 all = 1; 619 break; 620 case 'f': 621 count = 1; 622 break; 623 case 'h': 624 estimate = 1; 625 break; 626 #ifndef COMPAT 627 case 'k': 628 blocksize = 1024; 629 break; 630 #endif /* COMPAT */ 631 case 'v': 632 unused = 1; 633 break; 634 default: 635 usage(); 636 } 637 } 638 } 639 cnt = getmntinfo(&mp, MNT_NOWAIT); 640 if (all) { 641 for (; --cnt >= 0; mp++) { 642 if (strcmp(mp->f_fstypename, MOUNT_FFS) == 0 || 643 strcmp(mp->f_fstypename, "ufs") == 0) { 644 if ((nm = strrchr(mp->f_mntfromname, '/'))) { 645 snprintf(dev, sizeof(dev), "%sr%s", 646 _PATH_DEV, nm + 1); 647 nm = dev; 648 } else 649 nm = mp->f_mntfromname; 650 quot(nm, mp->f_mntonname); 651 } 652 } 653 } 654 for (; --argc >= 0; argv++) { 655 mountpoint = NULL; 656 nm = *argv; 657 658 /* Remove trailing slashes from name. */ 659 cp = nm + strlen(nm); 660 while (*(--cp) == '/' && cp != nm) 661 *cp = '\0'; 662 663 /* Look up the name in the mount table. */ 664 for (i = 0; i < cnt; i++) { 665 /* Remove trailing slashes from name. */ 666 cp = mp[i].f_mntonname + strlen(mp[i].f_mntonname); 667 while (*(--cp) == '/' && cp != mp[i].f_mntonname) 668 *cp = '\0'; 669 670 if ((!strcmp(mp->f_fstypename, MOUNT_FFS) || 671 !strcmp(mp->f_fstypename, MOUNT_MFS) || 672 !strcmp(mp->f_fstypename, "ufs")) && 673 strcmp(nm, mp[i].f_mntonname) == 0) { 674 nm = mp[i].f_mntfromname; 675 mountpoint = mp[i].f_mntonname; 676 break; 677 } 678 } 679 680 /* Make sure we have the raw device... */ 681 if (strncmp(nm, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0 && 682 nm[sizeof(_PATH_DEV) - 1] != 'r') { 683 snprintf(dev, sizeof(dev), "%sr%s", _PATH_DEV, 684 nm + sizeof(_PATH_DEV) - 1); 685 nm = dev; 686 } 687 quot(nm, mountpoint); 688 } 689 exit(0); 690 } 691