1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2017 OmniTI Computer Consulting, Inc. All rights reserved. 23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * du -- summarize disk usage 32 * du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...] 33 */ 34 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <sys/avl.h> 38 #include <fcntl.h> 39 #include <dirent.h> 40 #include <limits.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <unistd.h> 45 #include <locale.h> 46 #include <libcmdutils.h> 47 48 49 static int aflg = 0; 50 static int rflg = 0; 51 static int sflg = 0; 52 static int kflg = 0; 53 static int mflg = 0; 54 static int oflg = 0; 55 static int dflg = 0; 56 static int hflg = 0; 57 static int Aflg = 0; 58 static int Hflg = 0; 59 static int Lflg = 0; 60 static int cmdarg = 0; /* Command line argument */ 61 static char *dot = "."; 62 static int level = 0; /* Level of recursion */ 63 64 static char *base; 65 static char *name; 66 static size_t base_len = PATH_MAX + 1; /* # of chars for base */ 67 static size_t name_len = PATH_MAX + 1; /* # of chars for name */ 68 69 #define NUMBER_WIDTH 64 70 typedef char numbuf_t[NUMBER_WIDTH]; 71 72 /* 73 * Output formats. illumos uses a tab as separator, XPG4 a space. 74 */ 75 #ifdef XPG4 76 #define FORMAT1 "%s %s\n" 77 #define FORMAT2 "%lld %s\n" 78 #else 79 #define FORMAT1 "%s\t%s\n" 80 #define FORMAT2 "%lld\t%s\n" 81 #endif 82 83 /* 84 * convert DEV_BSIZE blocks to K blocks 85 */ 86 #define DEV_BSIZE 512 87 #define DEV_KSHIFT 1 88 #define DEV_MSHIFT 11 89 #define kb(n) (((u_longlong_t)(n)) >> DEV_KSHIFT) 90 #define mb(n) (((u_longlong_t)(n)) >> DEV_MSHIFT) 91 92 long wait(); 93 static u_longlong_t descend(char *curname, int curfd, int *retcode, 94 dev_t device); 95 static void printsize(blkcnt_t blocks, char *path); 96 static void exitdu(int exitcode); 97 98 static avl_tree_t *tree = NULL; 99 100 int 101 main(int argc, char **argv) 102 { 103 blkcnt_t blocks = 0; 104 int c; 105 extern int optind; 106 char *np; 107 pid_t pid, wpid; 108 int status, retcode = 0; 109 setbuf(stderr, NULL); 110 (void) setlocale(LC_ALL, ""); 111 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 112 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 113 #endif 114 (void) textdomain(TEXT_DOMAIN); 115 116 #ifdef XPG4 117 rflg++; /* "-r" is not an option but ON always */ 118 #endif 119 120 while ((c = getopt(argc, argv, "aAdhHkLmorsx")) != EOF) 121 switch (c) { 122 123 case 'a': 124 aflg++; 125 continue; 126 127 case 'h': 128 hflg++; 129 kflg = 0; 130 mflg = 0; 131 continue; 132 133 case 'r': 134 rflg++; 135 continue; 136 137 case 's': 138 sflg++; 139 continue; 140 141 case 'k': 142 kflg++; 143 hflg = 0; 144 mflg = 0; 145 continue; 146 147 case 'm': 148 mflg++; 149 hflg = 0; 150 kflg = 0; 151 continue; 152 153 case 'o': 154 oflg++; 155 continue; 156 157 case 'd': 158 dflg++; 159 continue; 160 161 case 'x': 162 dflg++; 163 continue; 164 165 case 'A': 166 Aflg++; 167 continue; 168 169 case 'H': 170 Hflg++; 171 /* -H and -L are mutually exclusive */ 172 Lflg = 0; 173 cmdarg++; 174 continue; 175 176 case 'L': 177 Lflg++; 178 /* -H and -L are mutually exclusive */ 179 Hflg = 0; 180 cmdarg = 0; 181 continue; 182 case '?': 183 (void) fprintf(stderr, gettext( 184 "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] " 185 "[file...]\n")); 186 exit(2); 187 } 188 if (optind == argc) { 189 argv = ˙ 190 argc = 1; 191 optind = 0; 192 } 193 194 /* "-o" and "-s" don't make any sense together. */ 195 if (oflg && sflg) 196 oflg = 0; 197 198 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) { 199 perror("du"); 200 exit(1); 201 } 202 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) { 203 perror("du"); 204 free(base); 205 exit(1); 206 } 207 do { 208 if (optind < argc - 1) { 209 pid = fork(); 210 if (pid == (pid_t)-1) { 211 perror(gettext("du: No more processes")); 212 exitdu(1); 213 } 214 if (pid != 0) { 215 while ((wpid = wait(&status)) != pid && 216 wpid != (pid_t)-1) 217 ; 218 if (pid != (pid_t)-1 && status != 0) 219 retcode = 1; 220 } 221 } 222 if (optind == argc - 1 || pid == 0) { 223 while (base_len < (strlen(argv[optind]) + 1)) { 224 base_len = base_len * 2; 225 if ((base = (char *)realloc(base, base_len * 226 sizeof (char))) == NULL) { 227 if (rflg) { 228 (void) fprintf(stderr, gettext( 229 "du: can't process %s"), 230 argv[optind]); 231 perror(""); 232 } 233 exitdu(1); 234 } 235 } 236 if (base_len > name_len) { 237 name_len = base_len; 238 if ((name = (char *)realloc(name, name_len * 239 sizeof (char))) == NULL) { 240 if (rflg) { 241 (void) fprintf(stderr, gettext( 242 "du: can't process %s"), 243 argv[optind]); 244 perror(""); 245 } 246 exitdu(1); 247 } 248 } 249 (void) strcpy(base, argv[optind]); 250 (void) strcpy(name, argv[optind]); 251 if (np = strrchr(name, '/')) { 252 *np++ = '\0'; 253 if (chdir(*name ? name : "/") < 0) { 254 if (rflg) { 255 (void) fprintf(stderr, "du: "); 256 perror(*name ? name : "/"); 257 exitdu(1); 258 } 259 exitdu(0); 260 } 261 } else 262 np = base; 263 blocks = descend(*np ? np : ".", 0, &retcode, 264 (dev_t)0); 265 if (sflg) 266 printsize(blocks, base); 267 if (optind < argc - 1) 268 exitdu(retcode); 269 } 270 optind++; 271 } while (optind < argc); 272 exitdu(retcode); 273 274 return (retcode); 275 } 276 277 /* 278 * descend recursively, adding up the allocated blocks. 279 * If curname is NULL, curfd is used. 280 */ 281 static u_longlong_t 282 descend(char *curname, int curfd, int *retcode, dev_t device) 283 { 284 static DIR *dirp = NULL; 285 char *ebase0, *ebase; 286 struct stat stb, stb1; 287 int i, j, ret, fd, tmpflg; 288 int follow_symlinks; 289 blkcnt_t blocks = 0; 290 off_t curoff = 0; 291 ptrdiff_t offset; 292 ptrdiff_t offset0; 293 struct dirent *dp; 294 char dirbuf[PATH_MAX + 1]; 295 u_longlong_t retval; 296 297 ebase0 = ebase = strchr(base, 0); 298 if (ebase > base && ebase[-1] == '/') 299 ebase--; 300 offset = ebase - base; 301 offset0 = ebase0 - base; 302 303 if (curname) 304 curfd = AT_FDCWD; 305 306 /* 307 * If neither a -L or a -H was specified, don't follow symlinks. 308 * If a -H was specified, don't follow symlinks if the file is 309 * not a command line argument. 310 */ 311 follow_symlinks = (Lflg || (Hflg && cmdarg)); 312 if (follow_symlinks) { 313 i = fstatat(curfd, curname, &stb, 0); 314 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW); 315 316 /* 317 * Make sure any files encountered while traversing the 318 * hierarchy are not considered command line arguments. 319 */ 320 if (Hflg) { 321 cmdarg = 0; 322 } 323 } else { 324 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW); 325 j = 0; 326 } 327 328 if ((i < 0) || (j < 0)) { 329 if (rflg) { 330 (void) fprintf(stderr, "du: "); 331 perror(base); 332 } 333 334 /* 335 * POSIX states that non-zero status codes are only set 336 * when an error message is printed out on stderr 337 */ 338 *retcode = (rflg ? 1 : 0); 339 *ebase0 = 0; 340 return (0); 341 } 342 if (device) { 343 if (dflg && stb.st_dev != device) { 344 *ebase0 = 0; 345 return (0); 346 } 347 } 348 else 349 device = stb.st_dev; 350 351 /* 352 * If following links (-L) we need to keep track of all inodes 353 * visited so they are only visited/reported once and cycles 354 * are avoided. Otherwise, only keep track of files which are 355 * hard links so they only get reported once, and of directories 356 * so we don't report a directory and its hierarchy more than 357 * once in the special case in which it lies under the 358 * hierarchy of a directory which is a hard link. 359 * Note: Files with multiple links should only be counted 360 * once. Since each inode could possibly be referenced by a 361 * symbolic link, we need to keep track of all inodes when -L 362 * is specified. 363 */ 364 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) || 365 (stb.st_nlink > 1)) { 366 int rc; 367 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) { 368 if (rc == 0) { 369 /* 370 * This hierarchy, or file with multiple 371 * links, has already been visited/reported. 372 */ 373 return (0); 374 } else { 375 /* 376 * An error occurred while trying to add the 377 * node to the tree. 378 */ 379 if (rflg) { 380 perror("du"); 381 } 382 exitdu(1); 383 } 384 } 385 } 386 blocks = Aflg ? stb.st_size : stb.st_blocks; 387 388 /* 389 * If there are extended attributes on the current file, add their 390 * block usage onto the block count. Note: Since pathconf() always 391 * follows symlinks, only test for extended attributes using pathconf() 392 * if we are following symlinks or the current file is not a symlink. 393 */ 394 if (curname && (follow_symlinks || 395 ((stb.st_mode & S_IFMT) != S_IFLNK)) && 396 pathconf(curname, _PC_XATTR_EXISTS) == 1) { 397 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) { 398 if (rflg) 399 perror(gettext( 400 "du: can't access extended attributes")); 401 } 402 else 403 { 404 tmpflg = sflg; 405 sflg = 1; 406 blocks += descend(NULL, fd, retcode, device); 407 sflg = tmpflg; 408 } 409 } 410 if ((stb.st_mode & S_IFMT) != S_IFDIR) { 411 /* 412 * Don't print twice: if sflg, file will get printed in main(). 413 * Otherwise, level == 0 means this file is listed on the 414 * command line, so print here; aflg means print all files. 415 */ 416 if (sflg == 0 && (aflg || level == 0)) 417 printsize(blocks, base); 418 return (blocks); 419 } 420 if (dirp != NULL) 421 /* 422 * Close the parent directory descriptor, we will reopen 423 * the directory when we pop up from this level of the 424 * recursion. 425 */ 426 (void) closedir(dirp); 427 if (curname == NULL) 428 dirp = fdopendir(curfd); 429 else 430 dirp = opendir(curname); 431 if (dirp == NULL) { 432 if (rflg) { 433 (void) fprintf(stderr, "du: "); 434 perror(base); 435 } 436 *retcode = 1; 437 *ebase0 = 0; 438 return (0); 439 } 440 level++; 441 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) { 442 if (getcwd(dirbuf, PATH_MAX) == NULL) { 443 if (rflg) { 444 (void) fprintf(stderr, "du: "); 445 perror(base); 446 } 447 exitdu(1); 448 } 449 } 450 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) { 451 if (rflg) { 452 (void) fprintf(stderr, "du: "); 453 perror(base); 454 } 455 *retcode = 1; 456 *ebase0 = 0; 457 (void) closedir(dirp); 458 dirp = NULL; 459 level--; 460 return (0); 461 } 462 while (dp = readdir(dirp)) { 463 if ((strcmp(dp->d_name, ".") == 0) || 464 (strcmp(dp->d_name, "..") == 0)) 465 continue; 466 /* 467 * we're about to append "/" + dp->d_name 468 * onto end of base; make sure there's enough 469 * space 470 */ 471 while ((offset + strlen(dp->d_name) + 2) > base_len) { 472 base_len = base_len * 2; 473 if ((base = (char *)realloc(base, 474 base_len * sizeof (char))) == NULL) { 475 if (rflg) { 476 perror("du"); 477 } 478 exitdu(1); 479 } 480 ebase = base + offset; 481 ebase0 = base + offset0; 482 } 483 /* LINTED - unbounded string specifier */ 484 (void) sprintf(ebase, "/%s", dp->d_name); 485 curoff = telldir(dirp); 486 retval = descend(ebase + 1, 0, retcode, device); 487 /* base may have been moved via realloc in descend() */ 488 ebase = base + offset; 489 ebase0 = base + offset0; 490 *ebase = 0; 491 blocks += retval; 492 if (dirp == NULL) { 493 if ((dirp = opendir(".")) == NULL) { 494 if (rflg) { 495 (void) fprintf(stderr, 496 gettext("du: Can't reopen in ")); 497 perror(base); 498 } 499 *retcode = 1; 500 level--; 501 return (0); 502 } 503 seekdir(dirp, curoff); 504 } 505 } 506 (void) closedir(dirp); 507 level--; 508 dirp = NULL; 509 if (sflg == 0) 510 printsize(blocks, base); 511 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) 512 ret = chdir(dirbuf); 513 else 514 ret = chdir(".."); 515 if (ret < 0) { 516 if (rflg) { 517 (void) sprintf(strchr(base, '\0'), "/.."); 518 (void) fprintf(stderr, 519 gettext("du: Can't change dir to '..' in ")); 520 perror(base); 521 } 522 exitdu(1); 523 } 524 *ebase0 = 0; 525 if (oflg) 526 return (0); 527 else 528 return (blocks); 529 } 530 531 /* 532 * Convert an unsigned long long to a string representation and place the 533 * result in the caller-supplied buffer. 534 * The given number is in units of "unit_from" size, 535 * this will first be converted to a number in 1024 or 1000 byte size, 536 * depending on the scaling factor. 537 * Then the number is scaled down until it is small enough to be in a good 538 * human readable format i.e. in the range 0 thru scale-1. 539 * If it's smaller than 10 there's room enough to provide one decimal place. 540 * The value "(unsigned long long)-1" is a special case and is always 541 * converted to "-1". 542 * Returns a pointer to the caller-supplied buffer. 543 */ 544 static char * 545 number_to_scaled_string( 546 numbuf_t buf, /* put the result here */ 547 unsigned long long number, /* convert this number */ 548 unsigned long long unit_from, /* number of bytes per input unit */ 549 unsigned long long scale) /* 1024 (-h) or 1000 (-H) */ 550 { 551 unsigned long long save = 0; 552 char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */ 553 char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */ 554 555 if ((long long)number == (long long)-1) { 556 (void) strcpy(buf, "-1"); 557 return (buf); 558 } 559 560 /* 561 * Convert number from unit_from to given scale (1024 or 1000) 562 * This means multiply number with unit_from and divide by scale. 563 * if number is large enough, we first divide and then multiply 564 * to avoid an overflow 565 * (large enough here means 100 (rather arbitrary value) 566 * times scale in order to reduce rounding errors) 567 * otherwise, we first multiply and then divide 568 * to avoid an underflow 569 */ 570 if (number >= 100L * scale) { 571 number = number / scale; 572 number = number * unit_from; 573 } else { 574 number = number * unit_from; 575 number = number / scale; 576 } 577 578 /* 579 * Now we have number as a count of scale units. 580 * Stop scaling when we reached exa bytes, then something is 581 * probably wrong with our number. 582 */ 583 while ((number >= scale) && (*uom != 'E')) { 584 uom++; /* next unit of measurement */ 585 save = number; 586 number = (number + (scale / 2)) / scale; 587 } 588 589 /* check if we should output a decimal place after the point */ 590 if (save && ((save / scale) < 10)) { 591 /* sprintf() will round for us */ 592 float fnum = (float)save / scale; 593 (void) sprintf(buf, "%4.1f%c", fnum, *uom); 594 } else { 595 (void) sprintf(buf, "%4llu%c", number, *uom); 596 } 597 return (buf); 598 } 599 600 static void 601 printsize(blkcnt_t blocks, char *path) 602 { 603 u_longlong_t bsize; 604 605 bsize = Aflg ? 1 : DEV_BSIZE; 606 607 if (hflg) { 608 numbuf_t numbuf; 609 unsigned long long scale = 1024L; 610 (void) printf(FORMAT1, 611 number_to_scaled_string(numbuf, blocks, bsize, scale), 612 path); 613 } else if (kflg) { 614 (void) printf(FORMAT2, (long long)kb(blocks), path); 615 } else if (mflg) { 616 (void) printf(FORMAT2, (long long)mb(blocks), path); 617 } else { 618 (void) printf(FORMAT2, (long long)blocks, path); 619 } 620 } 621 622 static void 623 exitdu(int exitcode) 624 { 625 free(base); 626 free(name); 627 exit(exitcode); 628 } 629