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