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