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 2006 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 blkcnt_t blocks = 0; 285 off_t curoff = 0; 286 ptrdiff_t offset; 287 ptrdiff_t offset0; 288 struct dirent *dp; 289 char dirbuf[PATH_MAX + 1]; 290 u_longlong_t retval; 291 292 ebase0 = ebase = strchr(base, 0); 293 if (ebase > base && ebase[-1] == '/') 294 ebase--; 295 offset = ebase - base; 296 offset0 = ebase0 - base; 297 298 if (curname) 299 curfd = AT_FDCWD; 300 301 /* 302 * If neither a -L or a -H was specified, don't follow symlinks. 303 * If a -H was specified, don't follow symlinks if the file is 304 * not a command line argument. 305 */ 306 if (((Lflg == 0) && (Hflg == 0)) || ((Hflg) && !(cmdarg))) { 307 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW); 308 j = 0; 309 } else { 310 i = fstatat(curfd, curname, &stb, 0); 311 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW); 312 313 /* 314 * Make sure any files encountered while traversing the 315 * hierarchy are not considered command line arguments. 316 */ 317 if (Hflg) { 318 cmdarg = 0; 319 } 320 } 321 322 if ((i < 0) || (j < 0)) { 323 if (rflg) { 324 (void) fprintf(stderr, "du: "); 325 perror(base); 326 } 327 328 /* 329 * POSIX states that non-zero status codes are only set 330 * when an error message is printed out on stderr 331 */ 332 *retcode = (rflg ? 1 : 0); 333 *ebase0 = 0; 334 return (0); 335 } 336 if (device) { 337 if (dflg && stb.st_dev != device) { 338 *ebase0 = 0; 339 return (0); 340 } 341 } 342 else 343 device = stb.st_dev; 344 345 /* 346 * If following links (-L) we need to keep track of all inodes 347 * visited so they are only visited/reported once and cycles 348 * are avoided. Otherwise, only keep track of files which are 349 * hard links so they only get reported once, and of directories 350 * so we don't report a directory and its hierarchy more than 351 * once in the special case in which it lies under the 352 * hierarchy of a directory which is a hard link. 353 * Note: Files with multiple links should only be counted 354 * once. Since each inode could possibly be referenced by a 355 * symbolic link, we need to keep track of all inodes when -L 356 * is specified. 357 */ 358 if ((Lflg) || ((stb.st_mode & S_IFMT) == S_IFDIR) || 359 (stb.st_nlink > 1)) { 360 int rc; 361 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) { 362 if (rc == 0) { 363 /* 364 * This hierarchy, or file with multiple 365 * links, has already been visited/reported. 366 */ 367 return (0); 368 } else { 369 /* 370 * An error occurred while trying to add the 371 * node to the tree. 372 */ 373 if (rflg) { 374 perror("du"); 375 } 376 exitdu(1); 377 } 378 } 379 } 380 blocks = stb.st_blocks; 381 /* 382 * If there are extended attributes on the current file, add their 383 * block usage onto the block count. 384 */ 385 if (curname && pathconf(curname, _PC_XATTR_EXISTS) == 1) { 386 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) { 387 if (rflg) 388 perror(gettext( 389 "du: can't access extended attributes")); 390 } 391 else 392 { 393 tmpflg = sflg; 394 sflg = 1; 395 blocks += descend(NULL, fd, retcode, device); 396 sflg = tmpflg; 397 } 398 } 399 if ((stb.st_mode & S_IFMT) != S_IFDIR) { 400 /* 401 * Don't print twice: if sflg, file will get printed in main(). 402 * Otherwise, level == 0 means this file is listed on the 403 * command line, so print here; aflg means print all files. 404 */ 405 if (sflg == 0 && (aflg || level == 0)) 406 printsize(blocks, base); 407 return (blocks); 408 } 409 if (dirp != NULL) 410 /* 411 * Close the parent directory descriptor, we will reopen 412 * the directory when we pop up from this level of the 413 * recursion. 414 */ 415 (void) closedir(dirp); 416 if (curname == NULL) 417 dirp = fdopendir(curfd); 418 else 419 dirp = opendir(curname); 420 if (dirp == NULL) { 421 if (rflg) { 422 (void) fprintf(stderr, "du: "); 423 perror(base); 424 } 425 *retcode = 1; 426 *ebase0 = 0; 427 return (0); 428 } 429 level++; 430 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) { 431 if (getcwd(dirbuf, PATH_MAX) == NULL) { 432 if (rflg) { 433 (void) fprintf(stderr, "du: "); 434 perror(base); 435 } 436 exitdu(1); 437 } 438 } 439 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) { 440 if (rflg) { 441 (void) fprintf(stderr, "du: "); 442 perror(base); 443 } 444 *retcode = 1; 445 *ebase0 = 0; 446 (void) closedir(dirp); 447 dirp = NULL; 448 level--; 449 return (0); 450 } 451 while (dp = readdir(dirp)) { 452 if ((strcmp(dp->d_name, ".") == 0) || 453 (strcmp(dp->d_name, "..") == 0)) 454 continue; 455 /* 456 * we're about to append "/" + dp->d_name 457 * onto end of base; make sure there's enough 458 * space 459 */ 460 while ((offset + strlen(dp->d_name) + 2) > base_len) { 461 base_len = base_len * 2; 462 if ((base = (char *)realloc(base, 463 base_len * sizeof (char))) == NULL) { 464 if (rflg) { 465 perror("du"); 466 } 467 exitdu(1); 468 } 469 ebase = base + offset; 470 ebase0 = base + offset0; 471 } 472 /* LINTED - unbounded string specifier */ 473 (void) sprintf(ebase, "/%s", dp->d_name); 474 curoff = telldir(dirp); 475 retval = descend(ebase + 1, 0, retcode, device); 476 /* base may have been moved via realloc in descend() */ 477 ebase = base + offset; 478 ebase0 = base + offset0; 479 *ebase = 0; 480 blocks += retval; 481 if (dirp == NULL) { 482 if ((dirp = opendir(".")) == NULL) { 483 if (rflg) { 484 (void) fprintf(stderr, 485 gettext("du: Can't reopen in ")); 486 perror(base); 487 } 488 *retcode = 1; 489 level--; 490 return (0); 491 } 492 seekdir(dirp, curoff); 493 } 494 } 495 (void) closedir(dirp); 496 level--; 497 dirp = NULL; 498 if (sflg == 0) 499 printsize(blocks, base); 500 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) 501 ret = chdir(dirbuf); 502 else 503 ret = chdir(".."); 504 if (ret < 0) { 505 if (rflg) { 506 (void) sprintf(strchr(base, '\0'), "/.."); 507 (void) fprintf(stderr, 508 gettext("du: Can't change dir to '..' in ")); 509 perror(base); 510 } 511 exitdu(1); 512 } 513 *ebase0 = 0; 514 if (oflg) 515 return (0); 516 else 517 return (blocks); 518 } 519 520 /* 521 * Convert an unsigned long long to a string representation and place the 522 * result in the caller-supplied buffer. 523 * The given number is in units of "unit_from" size, 524 * this will first be converted to a number in 1024 or 1000 byte size, 525 * depending on the scaling factor. 526 * Then the number is scaled down until it is small enough to be in a good 527 * human readable format i.e. in the range 0 thru scale-1. 528 * If it's smaller than 10 there's room enough to provide one decimal place. 529 * The value "(unsigned long long)-1" is a special case and is always 530 * converted to "-1". 531 * Returns a pointer to the caller-supplied buffer. 532 */ 533 static char * 534 number_to_scaled_string( 535 numbuf_t buf, /* put the result here */ 536 unsigned long long number, /* convert this number */ 537 unsigned long long unit_from, /* number of bytes per input unit */ 538 unsigned long long scale) /* 1024 (-h) or 1000 (-H) */ 539 { 540 unsigned long long save = 0; 541 char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */ 542 char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */ 543 544 if ((long long)number == (long long)-1) { 545 (void) strcpy(buf, "-1"); 546 return (buf); 547 } 548 549 /* 550 * Convert number from unit_from to given scale (1024 or 1000) 551 * This means multiply number with unit_from and divide by scale. 552 * if number is large enough, we first divide and then multiply 553 * to avoid an overflow 554 * (large enough here means 100 (rather arbitrary value) 555 * times scale in order to reduce rounding errors) 556 * otherwise, we first multiply and then divide 557 * to avoid an underflow 558 */ 559 if (number >= 100L * scale) { 560 number = number / scale; 561 number = number * unit_from; 562 } else { 563 number = number * unit_from; 564 number = number / scale; 565 } 566 567 /* 568 * Now we have number as a count of scale units. 569 * Stop scaling when we reached exa bytes, then something is 570 * probably wrong with our number. 571 */ 572 while ((number >= scale) && (*uom != 'E')) { 573 uom++; /* next unit of measurement */ 574 save = number; 575 number = (number + (scale / 2)) / scale; 576 } 577 578 /* check if we should output a decimal place after the point */ 579 if (save && ((save / scale) < 10)) { 580 /* sprintf() will round for us */ 581 float fnum = (float)save / scale; 582 (void) sprintf(buf, "%4.1f%c", fnum, *uom); 583 } else { 584 (void) sprintf(buf, "%4llu%c", number, *uom); 585 } 586 return (buf); 587 } 588 589 static void 590 printsize(blkcnt_t blocks, char *path) 591 { 592 if (hflg) { 593 numbuf_t numbuf; 594 unsigned long long scale = 1024L; 595 (void) printf(FORMAT1, 596 number_to_scaled_string(numbuf, blocks, DEV_BSIZE, scale), 597 path); 598 } else if (kflg) { 599 (void) printf(FORMAT2, (long long)kb(blocks), path); 600 } else if (mflg) { 601 (void) printf(FORMAT2, (long long)mb(blocks), path); 602 } else { 603 (void) printf(FORMAT2, (long long)blocks, path); 604 } 605 } 606 607 static void 608 exitdu(int exitcode) 609 { 610 free(base); 611 free(name); 612 exit(exitcode); 613 } 614