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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 /* 32 * du -- summarize disk usage 33 * du [-dorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...] 34 */ 35 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <sys/avl.h> 39 #include <fcntl.h> 40 #include <dirent.h> 41 #include <limits.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 #include <locale.h> 47 #include <libcmdutils.h> 48 49 50 static int aflg = 0; 51 static int rflg = 0; 52 static int sflg = 0; 53 static int kflg = 0; 54 static int mflg = 0; 55 static int oflg = 0; 56 static int dflg = 0; 57 static int hflg = 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. Solaris 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, "adhHkLmorsx")) != 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 'H': 166 Hflg++; 167 /* -H and -L are mutually exclusive */ 168 Lflg = 0; 169 cmdarg++; 170 continue; 171 172 case 'L': 173 Lflg++; 174 /* -H and -L are mutually exclusive */ 175 Hflg = 0; 176 cmdarg = 0; 177 continue; 178 case '?': 179 (void) fprintf(stderr, gettext( 180 "usage: du [-dorx] [-a|-s] [-h|-k|-m] [-H|-L] " 181 "[file...]\n")); 182 exit(2); 183 } 184 if (optind == argc) { 185 argv = ˙ 186 argc = 1; 187 optind = 0; 188 } 189 190 /* "-o" and "-s" don't make any sense together. */ 191 if (oflg && sflg) 192 oflg = 0; 193 194 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) { 195 perror("du"); 196 exit(1); 197 } 198 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) { 199 perror("du"); 200 free(base); 201 exit(1); 202 } 203 do { 204 if (optind < argc - 1) { 205 pid = fork(); 206 if (pid == (pid_t)-1) { 207 perror(gettext("du: No more processes")); 208 exitdu(1); 209 } 210 if (pid != 0) { 211 while ((wpid = wait(&status)) != pid && 212 wpid != (pid_t)-1) 213 ; 214 if (pid != (pid_t)-1 && status != 0) 215 retcode = 1; 216 } 217 } 218 if (optind == argc - 1 || pid == 0) { 219 while (base_len < (strlen(argv[optind]) + 1)) { 220 base_len = base_len * 2; 221 if ((base = (char *)realloc(base, base_len * 222 sizeof (char))) == NULL) { 223 if (rflg) { 224 (void) fprintf(stderr, gettext( 225 "du: can't process %s"), 226 argv[optind]); 227 perror(""); 228 } 229 exitdu(1); 230 } 231 } 232 if (base_len > name_len) { 233 name_len = base_len; 234 if ((name = (char *)realloc(name, name_len * 235 sizeof (char))) == NULL) { 236 if (rflg) { 237 (void) fprintf(stderr, gettext( 238 "du: can't process %s"), 239 argv[optind]); 240 perror(""); 241 } 242 exitdu(1); 243 } 244 } 245 (void) strcpy(base, argv[optind]); 246 (void) strcpy(name, argv[optind]); 247 if (np = strrchr(name, '/')) { 248 *np++ = '\0'; 249 if (chdir(*name ? name : "/") < 0) { 250 if (rflg) { 251 (void) fprintf(stderr, "du: "); 252 perror(*name ? name : "/"); 253 exitdu(1); 254 } 255 exitdu(0); 256 } 257 } else 258 np = base; 259 blocks = descend(*np ? np : ".", 0, &retcode, 260 (dev_t)0); 261 if (sflg) 262 printsize(blocks, base); 263 if (optind < argc - 1) 264 exitdu(retcode); 265 } 266 optind++; 267 } while (optind < argc); 268 exitdu(retcode); 269 270 return (retcode); 271 } 272 273 /* 274 * descend recursively, adding up the allocated blocks. 275 * If curname is NULL, curfd is used. 276 */ 277 static u_longlong_t 278 descend(char *curname, int curfd, int *retcode, dev_t device) 279 { 280 static DIR *dirp = NULL; 281 char *ebase0, *ebase; 282 struct stat stb, stb1; 283 int i, j, ret, fd, tmpflg; 284 int follow_symlinks; 285 blkcnt_t blocks = 0; 286 off_t curoff = 0; 287 ptrdiff_t offset; 288 ptrdiff_t offset0; 289 struct dirent *dp; 290 char dirbuf[PATH_MAX + 1]; 291 u_longlong_t retval; 292 293 ebase0 = ebase = strchr(base, 0); 294 if (ebase > base && ebase[-1] == '/') 295 ebase--; 296 offset = ebase - base; 297 offset0 = ebase0 - base; 298 299 if (curname) 300 curfd = AT_FDCWD; 301 302 /* 303 * If neither a -L or a -H was specified, don't follow symlinks. 304 * If a -H was specified, don't follow symlinks if the file is 305 * not a command line argument. 306 */ 307 follow_symlinks = (Lflg || (Hflg && cmdarg)); 308 if (follow_symlinks) { 309 i = fstatat(curfd, curname, &stb, 0); 310 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW); 311 312 /* 313 * Make sure any files encountered while traversing the 314 * hierarchy are not considered command line arguments. 315 */ 316 if (Hflg) { 317 cmdarg = 0; 318 } 319 } else { 320 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW); 321 j = 0; 322 } 323 324 if ((i < 0) || (j < 0)) { 325 if (rflg) { 326 (void) fprintf(stderr, "du: "); 327 perror(base); 328 } 329 330 /* 331 * POSIX states that non-zero status codes are only set 332 * when an error message is printed out on stderr 333 */ 334 *retcode = (rflg ? 1 : 0); 335 *ebase0 = 0; 336 return (0); 337 } 338 if (device) { 339 if (dflg && stb.st_dev != device) { 340 *ebase0 = 0; 341 return (0); 342 } 343 } 344 else 345 device = stb.st_dev; 346 347 /* 348 * If following links (-L) we need to keep track of all inodes 349 * visited so they are only visited/reported once and cycles 350 * are avoided. Otherwise, only keep track of files which are 351 * hard links so they only get reported once, and of directories 352 * so we don't report a directory and its hierarchy more than 353 * once in the special case in which it lies under the 354 * hierarchy of a directory which is a hard link. 355 * Note: Files with multiple links should only be counted 356 * once. Since each inode could possibly be referenced by a 357 * symbolic link, we need to keep track of all inodes when -L 358 * is specified. 359 */ 360 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) || 361 (stb.st_nlink > 1)) { 362 int rc; 363 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) { 364 if (rc == 0) { 365 /* 366 * This hierarchy, or file with multiple 367 * links, has already been visited/reported. 368 */ 369 return (0); 370 } else { 371 /* 372 * An error occurred while trying to add the 373 * node to the tree. 374 */ 375 if (rflg) { 376 perror("du"); 377 } 378 exitdu(1); 379 } 380 } 381 } 382 blocks = stb.st_blocks; 383 /* 384 * If there are extended attributes on the current file, add their 385 * block usage onto the block count. Note: Since pathconf() always 386 * follows symlinks, only test for extended attributes using pathconf() 387 * if we are following symlinks or the current file is not a symlink. 388 */ 389 if (curname && (follow_symlinks || 390 ((stb.st_mode & S_IFMT) != S_IFLNK)) && 391 pathconf(curname, _PC_XATTR_EXISTS) == 1) { 392 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) { 393 if (rflg) 394 perror(gettext( 395 "du: can't access extended attributes")); 396 } 397 else 398 { 399 tmpflg = sflg; 400 sflg = 1; 401 blocks += descend(NULL, fd, retcode, device); 402 sflg = tmpflg; 403 } 404 } 405 if ((stb.st_mode & S_IFMT) != S_IFDIR) { 406 /* 407 * Don't print twice: if sflg, file will get printed in main(). 408 * Otherwise, level == 0 means this file is listed on the 409 * command line, so print here; aflg means print all files. 410 */ 411 if (sflg == 0 && (aflg || level == 0)) 412 printsize(blocks, base); 413 return (blocks); 414 } 415 if (dirp != NULL) 416 /* 417 * Close the parent directory descriptor, we will reopen 418 * the directory when we pop up from this level of the 419 * recursion. 420 */ 421 (void) closedir(dirp); 422 if (curname == NULL) 423 dirp = fdopendir(curfd); 424 else 425 dirp = opendir(curname); 426 if (dirp == NULL) { 427 if (rflg) { 428 (void) fprintf(stderr, "du: "); 429 perror(base); 430 } 431 *retcode = 1; 432 *ebase0 = 0; 433 return (0); 434 } 435 level++; 436 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) { 437 if (getcwd(dirbuf, PATH_MAX) == NULL) { 438 if (rflg) { 439 (void) fprintf(stderr, "du: "); 440 perror(base); 441 } 442 exitdu(1); 443 } 444 } 445 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) { 446 if (rflg) { 447 (void) fprintf(stderr, "du: "); 448 perror(base); 449 } 450 *retcode = 1; 451 *ebase0 = 0; 452 (void) closedir(dirp); 453 dirp = NULL; 454 level--; 455 return (0); 456 } 457 while (dp = readdir(dirp)) { 458 if ((strcmp(dp->d_name, ".") == 0) || 459 (strcmp(dp->d_name, "..") == 0)) 460 continue; 461 /* 462 * we're about to append "/" + dp->d_name 463 * onto end of base; make sure there's enough 464 * space 465 */ 466 while ((offset + strlen(dp->d_name) + 2) > base_len) { 467 base_len = base_len * 2; 468 if ((base = (char *)realloc(base, 469 base_len * sizeof (char))) == NULL) { 470 if (rflg) { 471 perror("du"); 472 } 473 exitdu(1); 474 } 475 ebase = base + offset; 476 ebase0 = base + offset0; 477 } 478 /* LINTED - unbounded string specifier */ 479 (void) sprintf(ebase, "/%s", dp->d_name); 480 curoff = telldir(dirp); 481 retval = descend(ebase + 1, 0, retcode, device); 482 /* base may have been moved via realloc in descend() */ 483 ebase = base + offset; 484 ebase0 = base + offset0; 485 *ebase = 0; 486 blocks += retval; 487 if (dirp == NULL) { 488 if ((dirp = opendir(".")) == NULL) { 489 if (rflg) { 490 (void) fprintf(stderr, 491 gettext("du: Can't reopen in ")); 492 perror(base); 493 } 494 *retcode = 1; 495 level--; 496 return (0); 497 } 498 seekdir(dirp, curoff); 499 } 500 } 501 (void) closedir(dirp); 502 level--; 503 dirp = NULL; 504 if (sflg == 0) 505 printsize(blocks, base); 506 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) 507 ret = chdir(dirbuf); 508 else 509 ret = chdir(".."); 510 if (ret < 0) { 511 if (rflg) { 512 (void) sprintf(strchr(base, '\0'), "/.."); 513 (void) fprintf(stderr, 514 gettext("du: Can't change dir to '..' in ")); 515 perror(base); 516 } 517 exitdu(1); 518 } 519 *ebase0 = 0; 520 if (oflg) 521 return (0); 522 else 523 return (blocks); 524 } 525 526 /* 527 * Convert an unsigned long long to a string representation and place the 528 * result in the caller-supplied buffer. 529 * The given number is in units of "unit_from" size, 530 * this will first be converted to a number in 1024 or 1000 byte size, 531 * depending on the scaling factor. 532 * Then the number is scaled down until it is small enough to be in a good 533 * human readable format i.e. in the range 0 thru scale-1. 534 * If it's smaller than 10 there's room enough to provide one decimal place. 535 * The value "(unsigned long long)-1" is a special case and is always 536 * converted to "-1". 537 * Returns a pointer to the caller-supplied buffer. 538 */ 539 static char * 540 number_to_scaled_string( 541 numbuf_t buf, /* put the result here */ 542 unsigned long long number, /* convert this number */ 543 unsigned long long unit_from, /* number of bytes per input unit */ 544 unsigned long long scale) /* 1024 (-h) or 1000 (-H) */ 545 { 546 unsigned long long save = 0; 547 char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */ 548 char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */ 549 550 if ((long long)number == (long long)-1) { 551 (void) strcpy(buf, "-1"); 552 return (buf); 553 } 554 555 /* 556 * Convert number from unit_from to given scale (1024 or 1000) 557 * This means multiply number with unit_from and divide by scale. 558 * if number is large enough, we first divide and then multiply 559 * to avoid an overflow 560 * (large enough here means 100 (rather arbitrary value) 561 * times scale in order to reduce rounding errors) 562 * otherwise, we first multiply and then divide 563 * to avoid an underflow 564 */ 565 if (number >= 100L * scale) { 566 number = number / scale; 567 number = number * unit_from; 568 } else { 569 number = number * unit_from; 570 number = number / scale; 571 } 572 573 /* 574 * Now we have number as a count of scale units. 575 * Stop scaling when we reached exa bytes, then something is 576 * probably wrong with our number. 577 */ 578 while ((number >= scale) && (*uom != 'E')) { 579 uom++; /* next unit of measurement */ 580 save = number; 581 number = (number + (scale / 2)) / scale; 582 } 583 584 /* check if we should output a decimal place after the point */ 585 if (save && ((save / scale) < 10)) { 586 /* sprintf() will round for us */ 587 float fnum = (float)save / scale; 588 (void) sprintf(buf, "%4.1f%c", fnum, *uom); 589 } else { 590 (void) sprintf(buf, "%4llu%c", number, *uom); 591 } 592 return (buf); 593 } 594 595 static void 596 printsize(blkcnt_t blocks, char *path) 597 { 598 if (hflg) { 599 numbuf_t numbuf; 600 unsigned long long scale = 1024L; 601 (void) printf(FORMAT1, 602 number_to_scaled_string(numbuf, blocks, DEV_BSIZE, scale), 603 path); 604 } else if (kflg) { 605 (void) printf(FORMAT2, (long long)kb(blocks), path); 606 } else if (mflg) { 607 (void) printf(FORMAT2, (long long)mb(blocks), path); 608 } else { 609 (void) printf(FORMAT2, (long long)blocks, path); 610 } 611 } 612 613 static void 614 exitdu(int exitcode) 615 { 616 free(base); 617 free(name); 618 exit(exitcode); 619 } 620