1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1980, 1990, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * (c) UNIX System Laboratories, Inc. 7 * All or some portions of this file are derived from material licensed 8 * to the University of California by American Telephone and Telegraph 9 * Co. or Unix System Laboratories, Inc. and are reproduced herein with 10 * the permission of UNIX System Laboratories, Inc. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. 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 37 #if 0 38 #ifndef lint 39 static const char copyright[] = 40 "@(#) Copyright (c) 1980, 1990, 1993, 1994\n\ 41 The Regents of the University of California. All rights reserved.\n"; 42 #endif /* not lint */ 43 44 #ifndef lint 45 static char sccsid[] = "@(#)df.c 8.9 (Berkeley) 5/8/95"; 46 #endif /* not lint */ 47 #endif 48 #include <sys/cdefs.h> 49 __FBSDID("$FreeBSD$"); 50 51 #include <sys/param.h> 52 #include <sys/stat.h> 53 #include <sys/mount.h> 54 #include <sys/sysctl.h> 55 #include <err.h> 56 #include <getopt.h> 57 #include <libutil.h> 58 #include <locale.h> 59 #include <stdint.h> 60 #include <stdio.h> 61 #include <stdlib.h> 62 #include <string.h> 63 #include <sysexits.h> 64 #include <unistd.h> 65 #include <libxo/xo.h> 66 67 #include "extern.h" 68 69 #define UNITS_SI 1 70 #define UNITS_2 2 71 72 /* Maximum widths of various fields. */ 73 struct maxwidths { 74 int mntfrom; 75 int fstype; 76 int total; 77 int used; 78 int avail; 79 int iused; 80 int ifree; 81 }; 82 83 static void addstat(struct statfs *, struct statfs *); 84 static char *getmntpt(const char *); 85 static int int64width(int64_t); 86 static char *makenetvfslist(void); 87 static void prthuman(const struct statfs *, int64_t); 88 static void prthumanval(const char *, int64_t); 89 static intmax_t fsbtoblk(int64_t, uint64_t, u_long); 90 static void prtstat(struct statfs *, struct maxwidths *); 91 static size_t regetmntinfo(struct statfs **, long, const char **); 92 static void update_maxwidths(struct maxwidths *, const struct statfs *); 93 static void usage(void); 94 95 static __inline int 96 imax(int a, int b) 97 { 98 return (a > b ? a : b); 99 } 100 101 static int aflag = 0, cflag, hflag, iflag, kflag, lflag = 0, nflag, Tflag; 102 static int thousands; 103 104 static const struct option long_options[] = 105 { 106 { "si", no_argument, NULL, 'H' }, 107 { NULL, no_argument, NULL, 0 }, 108 }; 109 110 int 111 main(int argc, char *argv[]) 112 { 113 struct stat stbuf; 114 struct statfs statfsbuf, totalbuf; 115 struct maxwidths maxwidths; 116 struct statfs *mntbuf; 117 const char *fstype; 118 char *mntpt; 119 const char **vfslist; 120 int i, mntsize; 121 int ch, rv; 122 123 fstype = "ufs"; 124 (void)setlocale(LC_ALL, ""); 125 memset(&maxwidths, 0, sizeof(maxwidths)); 126 memset(&totalbuf, 0, sizeof(totalbuf)); 127 totalbuf.f_bsize = DEV_BSIZE; 128 strlcpy(totalbuf.f_mntfromname, "total", MNAMELEN); 129 vfslist = NULL; 130 131 argc = xo_parse_args(argc, argv); 132 if (argc < 0) 133 exit(1); 134 135 while ((ch = getopt_long(argc, argv, "+abcgHhiklmnPt:T,", long_options, 136 NULL)) != -1) 137 switch (ch) { 138 case 'a': 139 aflag = 1; 140 break; 141 case 'b': 142 /* FALLTHROUGH */ 143 case 'P': 144 /* 145 * POSIX specifically discusses the behavior of 146 * both -k and -P. It states that the blocksize should 147 * be set to 1024. Thus, if this occurs, simply break 148 * rather than clobbering the old blocksize. 149 */ 150 if (kflag) 151 break; 152 setenv("BLOCKSIZE", "512", 1); 153 hflag = 0; 154 break; 155 case 'c': 156 cflag = 1; 157 break; 158 case 'g': 159 setenv("BLOCKSIZE", "1g", 1); 160 hflag = 0; 161 break; 162 case 'H': 163 hflag = UNITS_SI; 164 break; 165 case 'h': 166 hflag = UNITS_2; 167 break; 168 case 'i': 169 iflag = 1; 170 break; 171 case 'k': 172 kflag++; 173 setenv("BLOCKSIZE", "1024", 1); 174 hflag = 0; 175 break; 176 case 'l': 177 /* Ignore duplicate -l */ 178 if (lflag) 179 break; 180 if (vfslist != NULL) 181 xo_errx(1, "-l and -t are mutually exclusive."); 182 vfslist = makevfslist(makenetvfslist()); 183 lflag = 1; 184 break; 185 case 'm': 186 setenv("BLOCKSIZE", "1m", 1); 187 hflag = 0; 188 break; 189 case 'n': 190 nflag = 1; 191 break; 192 case 't': 193 if (lflag) 194 xo_errx(1, "-l and -t are mutually exclusive."); 195 if (vfslist != NULL) 196 xo_errx(1, "only one -t option may be specified"); 197 fstype = optarg; 198 vfslist = makevfslist(optarg); 199 break; 200 case 'T': 201 Tflag = 1; 202 break; 203 case ',': 204 thousands = 1; 205 break; 206 case '?': 207 default: 208 usage(); 209 } 210 argc -= optind; 211 argv += optind; 212 213 rv = 0; 214 if (!*argv) { 215 /* everything (modulo -t) */ 216 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 217 mntsize = regetmntinfo(&mntbuf, mntsize, vfslist); 218 } else { 219 /* just the filesystems specified on the command line */ 220 mntbuf = malloc(argc * sizeof(*mntbuf)); 221 if (mntbuf == NULL) 222 xo_err(1, "malloc()"); 223 mntsize = 0; 224 /* continued in for loop below */ 225 } 226 227 xo_open_container("storage-system-information"); 228 xo_open_list("filesystem"); 229 230 /* iterate through specified filesystems */ 231 for (; *argv; argv++) { 232 if (stat(*argv, &stbuf) < 0) { 233 if ((mntpt = getmntpt(*argv)) == NULL) { 234 xo_warn("%s", *argv); 235 rv = 1; 236 continue; 237 } 238 } else if (S_ISCHR(stbuf.st_mode)) { 239 mntpt = getmntpt(*argv); 240 if (mntpt == NULL) { 241 xo_warnx("%s: not mounted", *argv); 242 rv = 1; 243 continue; 244 } 245 } else { 246 mntpt = *argv; 247 } 248 249 /* 250 * Statfs does not take a `wait' flag, so we cannot 251 * implement nflag here. 252 */ 253 if (statfs(mntpt, &statfsbuf) < 0) { 254 xo_warn("%s", mntpt); 255 rv = 1; 256 continue; 257 } 258 259 /* 260 * Check to make sure the arguments we've been given are 261 * satisfied. Return an error if we have been asked to 262 * list a mount point that does not match the other args 263 * we've been given (-l, -t, etc.). 264 */ 265 if (checkvfsname(statfsbuf.f_fstypename, vfslist)) { 266 rv = 1; 267 continue; 268 } 269 270 /* the user asked for it, so ignore the ignore flag */ 271 statfsbuf.f_flags &= ~MNT_IGNORE; 272 273 /* add to list */ 274 mntbuf[mntsize++] = statfsbuf; 275 } 276 277 memset(&maxwidths, 0, sizeof(maxwidths)); 278 for (i = 0; i < mntsize; i++) { 279 if (aflag || (mntbuf[i].f_flags & MNT_IGNORE) == 0) { 280 update_maxwidths(&maxwidths, &mntbuf[i]); 281 if (cflag) 282 addstat(&totalbuf, &mntbuf[i]); 283 } 284 } 285 for (i = 0; i < mntsize; i++) 286 if (aflag || (mntbuf[i].f_flags & MNT_IGNORE) == 0) 287 prtstat(&mntbuf[i], &maxwidths); 288 289 xo_close_list("filesystem"); 290 291 if (cflag) 292 prtstat(&totalbuf, &maxwidths); 293 294 xo_close_container("storage-system-information"); 295 xo_finish(); 296 exit(rv); 297 } 298 299 static char * 300 getmntpt(const char *name) 301 { 302 size_t mntsize, i; 303 struct statfs *mntbuf; 304 305 mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); 306 for (i = 0; i < mntsize; i++) { 307 if (!strcmp(mntbuf[i].f_mntfromname, name)) 308 return (mntbuf[i].f_mntonname); 309 } 310 return (NULL); 311 } 312 313 /* 314 * Make a pass over the file system info in ``mntbuf'' filtering out 315 * file system types not in vfslist and possibly re-stating to get 316 * current (not cached) info. Returns the new count of valid statfs bufs. 317 */ 318 static size_t 319 regetmntinfo(struct statfs **mntbufp, long mntsize, const char **vfslist) 320 { 321 int error, i, j; 322 struct statfs *mntbuf; 323 324 if (vfslist == NULL) 325 return (nflag ? mntsize : getmntinfo(mntbufp, MNT_WAIT)); 326 327 mntbuf = *mntbufp; 328 for (j = 0, i = 0; i < mntsize; i++) { 329 if (checkvfsname(mntbuf[i].f_fstypename, vfslist)) 330 continue; 331 /* 332 * XXX statfs(2) can fail for various reasons. It may be 333 * possible that the user does not have access to the 334 * pathname, if this happens, we will fall back on 335 * "stale" filesystem statistics. 336 */ 337 error = statfs(mntbuf[i].f_mntonname, &mntbuf[j]); 338 if (nflag || error < 0) 339 if (i != j) { 340 if (error < 0) 341 xo_warnx("%s stats possibly stale", 342 mntbuf[i].f_mntonname); 343 mntbuf[j] = mntbuf[i]; 344 } 345 j++; 346 } 347 return (j); 348 } 349 350 static void 351 prthuman(const struct statfs *sfsp, int64_t used) 352 { 353 354 prthumanval(" {:blocks/%6s}", sfsp->f_blocks * sfsp->f_bsize); 355 prthumanval(" {:used/%6s}", used * sfsp->f_bsize); 356 prthumanval(" {:available/%6s}", sfsp->f_bavail * sfsp->f_bsize); 357 } 358 359 static void 360 prthumanval(const char *fmt, int64_t bytes) 361 { 362 char buf[6]; 363 int flags; 364 365 flags = HN_B | HN_NOSPACE | HN_DECIMAL; 366 if (hflag == UNITS_SI) 367 flags |= HN_DIVISOR_1000; 368 369 humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1), 370 bytes, "", HN_AUTOSCALE, flags); 371 372 xo_attr("value", "%lld", (long long) bytes); 373 xo_emit(fmt, buf); 374 } 375 376 /* 377 * Print an inode count in "human-readable" format. 378 */ 379 static void 380 prthumanvalinode(const char *fmt, int64_t bytes) 381 { 382 char buf[6]; 383 int flags; 384 385 flags = HN_NOSPACE | HN_DECIMAL | HN_DIVISOR_1000; 386 387 humanize_number(buf, sizeof(buf) - (bytes < 0 ? 0 : 1), 388 bytes, "", HN_AUTOSCALE, flags); 389 390 xo_attr("value", "%lld", (long long) bytes); 391 xo_emit(fmt, buf); 392 } 393 394 /* 395 * Convert statfs returned file system size into BLOCKSIZE units. 396 */ 397 static intmax_t 398 fsbtoblk(int64_t num, uint64_t fsbs, u_long bs) 399 { 400 return (num * (intmax_t) fsbs / (int64_t) bs); 401 } 402 403 /* 404 * Print out status about a file system. 405 */ 406 static void 407 prtstat(struct statfs *sfsp, struct maxwidths *mwp) 408 { 409 static long blocksize; 410 static int headerlen, timesthrough = 0; 411 static const char *header; 412 int64_t used, availblks, inodes; 413 const char *format; 414 415 if (++timesthrough == 1) { 416 mwp->mntfrom = imax(mwp->mntfrom, (int)strlen("Filesystem")); 417 mwp->fstype = imax(mwp->fstype, (int)strlen("Type")); 418 if (thousands) { /* make space for commas */ 419 mwp->total += (mwp->total - 1) / 3; 420 mwp->used += (mwp->used - 1) / 3; 421 mwp->avail += (mwp->avail - 1) / 3; 422 mwp->iused += (mwp->iused - 1) / 3; 423 mwp->ifree += (mwp->ifree - 1) / 3; 424 } 425 if (hflag) { 426 header = " Size"; 427 mwp->total = mwp->used = mwp->avail = 428 (int)strlen(header); 429 } else { 430 header = getbsize(&headerlen, &blocksize); 431 mwp->total = imax(mwp->total, headerlen); 432 } 433 mwp->used = imax(mwp->used, (int)strlen("Used")); 434 mwp->avail = imax(mwp->avail, (int)strlen("Avail")); 435 436 xo_emit("{T:/%-*s}", mwp->mntfrom, "Filesystem"); 437 if (Tflag) 438 xo_emit(" {T:/%-*s}", mwp->fstype, "Type"); 439 xo_emit(" {T:/%*s} {T:/%*s} {T:/%*s} {T:Capacity}", 440 mwp->total, header, 441 mwp->used, "Used", mwp->avail, "Avail"); 442 if (iflag) { 443 mwp->iused = imax(hflag ? 0 : mwp->iused, 444 (int)strlen(" iused")); 445 mwp->ifree = imax(hflag ? 0 : mwp->ifree, 446 (int)strlen("ifree")); 447 xo_emit(" {T:/%*s} {T:/%*s} {T:\%iused}", 448 mwp->iused - 2, "iused", mwp->ifree, "ifree"); 449 } 450 xo_emit(" {T:Mounted on}\n"); 451 } 452 453 xo_open_instance("filesystem"); 454 /* Check for 0 block size. Can this happen? */ 455 if (sfsp->f_bsize == 0) { 456 xo_warnx ("File system %s does not have a block size, assuming 512.", 457 sfsp->f_mntonname); 458 sfsp->f_bsize = 512; 459 } 460 xo_emit("{tk:name/%-*s}", mwp->mntfrom, sfsp->f_mntfromname); 461 if (Tflag) 462 xo_emit(" {:type/%-*s}", mwp->fstype, sfsp->f_fstypename); 463 used = sfsp->f_blocks - sfsp->f_bfree; 464 availblks = sfsp->f_bavail + used; 465 if (hflag) { 466 prthuman(sfsp, used); 467 } else { 468 if (thousands) 469 format = " {t:total-blocks/%*j'd} {t:used-blocks/%*j'd} " 470 "{t:available-blocks/%*j'd}"; 471 else 472 format = " {t:total-blocks/%*jd} {t:used-blocks/%*jd} " 473 "{t:available-blocks/%*jd}"; 474 xo_emit(format, 475 mwp->total, fsbtoblk(sfsp->f_blocks, 476 sfsp->f_bsize, blocksize), 477 mwp->used, fsbtoblk(used, sfsp->f_bsize, blocksize), 478 mwp->avail, fsbtoblk(sfsp->f_bavail, 479 sfsp->f_bsize, blocksize)); 480 } 481 xo_emit(" {:used-percent/%5.0f}{U:%%}", 482 availblks == 0 ? 100.0 : (double)used / (double)availblks * 100.0); 483 if (iflag) { 484 inodes = sfsp->f_files; 485 used = inodes - sfsp->f_ffree; 486 if (hflag) { 487 xo_emit(" "); 488 prthumanvalinode(" {:inodes-used/%5s}", used); 489 prthumanvalinode(" {:inodes-free/%5s}", sfsp->f_ffree); 490 } else { 491 if (thousands) 492 format = " {:inodes-used/%*j'd} {:inodes-free/%*j'd}"; 493 else 494 format = " {:inodes-used/%*jd} {:inodes-free/%*jd}"; 495 xo_emit(format, mwp->iused, (intmax_t)used, 496 mwp->ifree, (intmax_t)sfsp->f_ffree); 497 } 498 xo_emit(" {:inodes-used-percent/%4.0f}{U:%%} ", 499 inodes == 0 ? 100.0 : 500 (double)used / (double)inodes * 100.0); 501 } else 502 xo_emit(" "); 503 if (strncmp(sfsp->f_mntfromname, "total", MNAMELEN) != 0) 504 xo_emit(" {:mounted-on}", sfsp->f_mntonname); 505 xo_emit("\n"); 506 xo_close_instance("filesystem"); 507 } 508 509 static void 510 addstat(struct statfs *totalfsp, struct statfs *statfsp) 511 { 512 uint64_t bsize; 513 514 bsize = statfsp->f_bsize / totalfsp->f_bsize; 515 totalfsp->f_blocks += statfsp->f_blocks * bsize; 516 totalfsp->f_bfree += statfsp->f_bfree * bsize; 517 totalfsp->f_bavail += statfsp->f_bavail * bsize; 518 totalfsp->f_files += statfsp->f_files; 519 totalfsp->f_ffree += statfsp->f_ffree; 520 } 521 522 /* 523 * Update the maximum field-width information in `mwp' based on 524 * the file system specified by `sfsp'. 525 */ 526 static void 527 update_maxwidths(struct maxwidths *mwp, const struct statfs *sfsp) 528 { 529 static long blocksize = 0; 530 int dummy; 531 532 if (blocksize == 0) 533 getbsize(&dummy, &blocksize); 534 535 mwp->mntfrom = imax(mwp->mntfrom, (int)strlen(sfsp->f_mntfromname)); 536 mwp->fstype = imax(mwp->fstype, (int)strlen(sfsp->f_fstypename)); 537 mwp->total = imax(mwp->total, int64width( 538 fsbtoblk((int64_t)sfsp->f_blocks, sfsp->f_bsize, blocksize))); 539 mwp->used = imax(mwp->used, 540 int64width(fsbtoblk((int64_t)sfsp->f_blocks - 541 (int64_t)sfsp->f_bfree, sfsp->f_bsize, blocksize))); 542 mwp->avail = imax(mwp->avail, int64width(fsbtoblk(sfsp->f_bavail, 543 sfsp->f_bsize, blocksize))); 544 mwp->iused = imax(mwp->iused, int64width((int64_t)sfsp->f_files - 545 sfsp->f_ffree)); 546 mwp->ifree = imax(mwp->ifree, int64width(sfsp->f_ffree)); 547 } 548 549 /* Return the width in characters of the specified value. */ 550 static int 551 int64width(int64_t val) 552 { 553 int len; 554 555 len = 0; 556 /* Negative or zero values require one extra digit. */ 557 if (val <= 0) { 558 val = -val; 559 len++; 560 } 561 while (val > 0) { 562 len++; 563 val /= 10; 564 } 565 566 return (len); 567 } 568 569 static void 570 usage(void) 571 { 572 573 xo_error( 574 "usage: df [-b | -g | -H | -h | -k | -m | -P] [-acilnT] [-t type] [-,]\n" 575 " [file | filesystem ...]\n"); 576 exit(EX_USAGE); 577 } 578 579 static char * 580 makenetvfslist(void) 581 { 582 char *str, *strptr, **listptr; 583 struct xvfsconf *xvfsp, *keep_xvfsp; 584 size_t buflen; 585 int cnt, i, maxvfsconf; 586 587 if (sysctlbyname("vfs.conflist", NULL, &buflen, NULL, 0) < 0) { 588 xo_warn("sysctl(vfs.conflist)"); 589 return (NULL); 590 } 591 xvfsp = malloc(buflen); 592 if (xvfsp == NULL) { 593 xo_warnx("malloc failed"); 594 return (NULL); 595 } 596 keep_xvfsp = xvfsp; 597 if (sysctlbyname("vfs.conflist", xvfsp, &buflen, NULL, 0) < 0) { 598 xo_warn("sysctl(vfs.conflist)"); 599 free(keep_xvfsp); 600 return (NULL); 601 } 602 maxvfsconf = buflen / sizeof(struct xvfsconf); 603 604 if ((listptr = malloc(sizeof(char*) * maxvfsconf)) == NULL) { 605 xo_warnx("malloc failed"); 606 free(keep_xvfsp); 607 return (NULL); 608 } 609 610 for (cnt = 0, i = 0; i < maxvfsconf; i++) { 611 if (xvfsp->vfc_flags & VFCF_NETWORK) { 612 listptr[cnt++] = strdup(xvfsp->vfc_name); 613 if (listptr[cnt-1] == NULL) { 614 xo_warnx("malloc failed"); 615 free(listptr); 616 free(keep_xvfsp); 617 return (NULL); 618 } 619 } 620 xvfsp++; 621 } 622 623 if (cnt == 0 || 624 (str = malloc(sizeof(char) * (32 * cnt + cnt + 2))) == NULL) { 625 if (cnt > 0) 626 xo_warnx("malloc failed"); 627 free(listptr); 628 free(keep_xvfsp); 629 return (NULL); 630 } 631 632 *str = 'n'; *(str + 1) = 'o'; 633 for (i = 0, strptr = str + 2; i < cnt; i++, strptr++) { 634 strlcpy(strptr, listptr[i], 32); 635 strptr += strlen(listptr[i]); 636 *strptr = ','; 637 free(listptr[i]); 638 } 639 *(--strptr) = '\0'; 640 641 free(keep_xvfsp); 642 free(listptr); 643 return (str); 644 } 645