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 * Copyright 2017 Jason King 26 */ 27 28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 29 /* All Rights Reserved */ 30 31 /* 32 * du -- summarize disk usage 33 * du [-Adorx] [-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 <sys/sysmacros.h> 40 #include <fcntl.h> 41 #include <dirent.h> 42 #include <limits.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 #include <locale.h> 48 #include <libcmdutils.h> 49 50 51 static int aflg = 0; 52 static int rflg = 0; 53 static int sflg = 0; 54 static int kflg = 0; 55 static int mflg = 0; 56 static int oflg = 0; 57 static int dflg = 0; 58 static int hflg = 0; 59 static int Aflg = 0; 60 static int Hflg = 0; 61 static int Lflg = 0; 62 static int cmdarg = 0; /* Command line argument */ 63 static char *dot = "."; 64 static int level = 0; /* Level of recursion */ 65 66 static char *base; 67 static char *name; 68 static size_t base_len = PATH_MAX + 1; /* # of chars for base */ 69 static size_t name_len = PATH_MAX + 1; /* # of chars for name */ 70 71 /* 72 * Output formats. illumos uses a tab as separator, XPG4 a space. 73 */ 74 #ifdef XPG4 75 #define FORMAT1 "%s %s\n" 76 #define FORMAT2 "%llu %s\n" 77 #else 78 #define FORMAT1 "%s\t%s\n" 79 #define FORMAT2 "%llu\t%s\n" 80 #endif 81 82 /* 83 * convert bytes to blocks 84 */ 85 #define DEV_BSHIFT 9 86 #define DEV_KSHIFT 10 87 #define DEV_MSHIFT 20 88 89 long wait(); 90 static u_longlong_t descend(char *curname, int curfd, int *retcode, 91 dev_t device); 92 static void printsize(blkcnt_t blocks, char *path); 93 static void exitdu(int exitcode); 94 95 static avl_tree_t *tree = NULL; 96 97 int 98 main(int argc, char **argv) 99 { 100 blkcnt_t blocks = 0; 101 int c; 102 extern int optind; 103 char *np; 104 pid_t pid, wpid; 105 int status, retcode = 0; 106 setbuf(stderr, NULL); 107 (void) setlocale(LC_ALL, ""); 108 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 109 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 110 #endif 111 (void) textdomain(TEXT_DOMAIN); 112 113 #ifdef XPG4 114 rflg++; /* "-r" is not an option but ON always */ 115 #endif 116 117 while ((c = getopt(argc, argv, "aAdhHkLmorsx")) != EOF) 118 switch (c) { 119 120 case 'a': 121 aflg++; 122 continue; 123 124 case 'h': 125 hflg++; 126 kflg = 0; 127 mflg = 0; 128 continue; 129 130 case 'r': 131 rflg++; 132 continue; 133 134 case 's': 135 sflg++; 136 continue; 137 138 case 'k': 139 kflg++; 140 hflg = 0; 141 mflg = 0; 142 continue; 143 144 case 'm': 145 mflg++; 146 hflg = 0; 147 kflg = 0; 148 continue; 149 150 case 'o': 151 oflg++; 152 continue; 153 154 case 'd': 155 dflg++; 156 continue; 157 158 case 'x': 159 dflg++; 160 continue; 161 162 case 'A': 163 Aflg++; 164 continue; 165 166 case 'H': 167 Hflg++; 168 /* -H and -L are mutually exclusive */ 169 Lflg = 0; 170 cmdarg++; 171 continue; 172 173 case 'L': 174 Lflg++; 175 /* -H and -L are mutually exclusive */ 176 Hflg = 0; 177 cmdarg = 0; 178 continue; 179 case '?': 180 (void) fprintf(stderr, gettext( 181 "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] " 182 "[file...]\n")); 183 exit(2); 184 } 185 if (optind == argc) { 186 argv = ˙ 187 argc = 1; 188 optind = 0; 189 } 190 191 /* "-o" and "-s" don't make any sense together. */ 192 if (oflg && sflg) 193 oflg = 0; 194 195 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) { 196 perror("du"); 197 exit(1); 198 } 199 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) { 200 perror("du"); 201 free(base); 202 exit(1); 203 } 204 do { 205 if (optind < argc - 1) { 206 pid = fork(); 207 if (pid == (pid_t)-1) { 208 perror(gettext("du: No more processes")); 209 exitdu(1); 210 } 211 if (pid != 0) { 212 while ((wpid = wait(&status)) != pid && 213 wpid != (pid_t)-1) 214 ; 215 if (pid != (pid_t)-1 && status != 0) 216 retcode = 1; 217 } 218 } 219 if (optind == argc - 1 || pid == 0) { 220 while (base_len < (strlen(argv[optind]) + 1)) { 221 base_len = base_len * 2; 222 if ((base = (char *)realloc(base, base_len * 223 sizeof (char))) == NULL) { 224 if (rflg) { 225 (void) fprintf(stderr, gettext( 226 "du: can't process %s"), 227 argv[optind]); 228 perror(""); 229 } 230 exitdu(1); 231 } 232 } 233 if (base_len > name_len) { 234 name_len = base_len; 235 if ((name = (char *)realloc(name, name_len * 236 sizeof (char))) == NULL) { 237 if (rflg) { 238 (void) fprintf(stderr, gettext( 239 "du: can't process %s"), 240 argv[optind]); 241 perror(""); 242 } 243 exitdu(1); 244 } 245 } 246 (void) strcpy(base, argv[optind]); 247 (void) strcpy(name, argv[optind]); 248 if (np = strrchr(name, '/')) { 249 *np++ = '\0'; 250 if (chdir(*name ? name : "/") < 0) { 251 if (rflg) { 252 (void) fprintf(stderr, "du: "); 253 perror(*name ? name : "/"); 254 exitdu(1); 255 } 256 exitdu(0); 257 } 258 } else 259 np = base; 260 blocks = descend(*np ? np : ".", 0, &retcode, 261 (dev_t)0); 262 if (sflg) 263 printsize(blocks, base); 264 if (optind < argc - 1) 265 exitdu(retcode); 266 } 267 optind++; 268 } while (optind < argc); 269 exitdu(retcode); 270 271 return (retcode); 272 } 273 274 /* 275 * descend recursively, adding up the allocated blocks. 276 * If curname is NULL, curfd is used. 277 */ 278 static u_longlong_t 279 descend(char *curname, int curfd, int *retcode, dev_t device) 280 { 281 static DIR *dirp = NULL; 282 char *ebase0, *ebase; 283 struct stat stb, stb1; 284 int i, j, ret, fd, tmpflg; 285 int follow_symlinks; 286 blkcnt_t blocks = 0; 287 off_t curoff = 0; 288 ptrdiff_t offset; 289 ptrdiff_t offset0; 290 struct dirent *dp; 291 char dirbuf[PATH_MAX + 1]; 292 u_longlong_t retval; 293 294 ebase0 = ebase = strchr(base, 0); 295 if (ebase > base && ebase[-1] == '/') 296 ebase--; 297 offset = ebase - base; 298 offset0 = ebase0 - base; 299 300 if (curname) 301 curfd = AT_FDCWD; 302 303 /* 304 * If neither a -L or a -H was specified, don't follow symlinks. 305 * If a -H was specified, don't follow symlinks if the file is 306 * not a command line argument. 307 */ 308 follow_symlinks = (Lflg || (Hflg && cmdarg)); 309 if (follow_symlinks) { 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 } else { 321 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW); 322 j = 0; 323 } 324 325 if ((i < 0) || (j < 0)) { 326 if (rflg) { 327 (void) fprintf(stderr, "du: "); 328 perror(base); 329 } 330 331 /* 332 * POSIX states that non-zero status codes are only set 333 * when an error message is printed out on stderr 334 */ 335 *retcode = (rflg ? 1 : 0); 336 *ebase0 = 0; 337 return (0); 338 } 339 if (device) { 340 if (dflg && stb.st_dev != device) { 341 *ebase0 = 0; 342 return (0); 343 } 344 } 345 else 346 device = stb.st_dev; 347 348 /* 349 * If following links (-L) we need to keep track of all inodes 350 * visited so they are only visited/reported once and cycles 351 * are avoided. Otherwise, only keep track of files which are 352 * hard links so they only get reported once, and of directories 353 * so we don't report a directory and its hierarchy more than 354 * once in the special case in which it lies under the 355 * hierarchy of a directory which is a hard link. 356 * Note: Files with multiple links should only be counted 357 * once. Since each inode could possibly be referenced by a 358 * symbolic link, we need to keep track of all inodes when -L 359 * is specified. 360 */ 361 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) || 362 (stb.st_nlink > 1)) { 363 int rc; 364 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) { 365 if (rc == 0) { 366 /* 367 * This hierarchy, or file with multiple 368 * links, has already been visited/reported. 369 */ 370 return (0); 371 } else { 372 /* 373 * An error occurred while trying to add the 374 * node to the tree. 375 */ 376 if (rflg) { 377 perror("du"); 378 } 379 exitdu(1); 380 } 381 } 382 } 383 blocks = Aflg ? stb.st_size : stb.st_blocks; 384 385 /* 386 * If there are extended attributes on the current file, add their 387 * block usage onto the block count. Note: Since pathconf() always 388 * follows symlinks, only test for extended attributes using pathconf() 389 * if we are following symlinks or the current file is not a symlink. 390 */ 391 if (curname && (follow_symlinks || 392 ((stb.st_mode & S_IFMT) != S_IFLNK)) && 393 pathconf(curname, _PC_XATTR_EXISTS) == 1) { 394 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) { 395 if (rflg) 396 perror(gettext( 397 "du: can't access extended attributes")); 398 } 399 else 400 { 401 tmpflg = sflg; 402 sflg = 1; 403 blocks += descend(NULL, fd, retcode, device); 404 sflg = tmpflg; 405 } 406 } 407 if ((stb.st_mode & S_IFMT) != S_IFDIR) { 408 /* 409 * Don't print twice: if sflg, file will get printed in main(). 410 * Otherwise, level == 0 means this file is listed on the 411 * command line, so print here; aflg means print all files. 412 */ 413 if (sflg == 0 && (aflg || level == 0)) 414 printsize(blocks, base); 415 return (blocks); 416 } 417 if (dirp != NULL) 418 /* 419 * Close the parent directory descriptor, we will reopen 420 * the directory when we pop up from this level of the 421 * recursion. 422 */ 423 (void) closedir(dirp); 424 if (curname == NULL) 425 dirp = fdopendir(curfd); 426 else 427 dirp = opendir(curname); 428 if (dirp == NULL) { 429 if (rflg) { 430 (void) fprintf(stderr, "du: "); 431 perror(base); 432 } 433 *retcode = 1; 434 *ebase0 = 0; 435 return (0); 436 } 437 level++; 438 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) { 439 if (getcwd(dirbuf, PATH_MAX) == NULL) { 440 if (rflg) { 441 (void) fprintf(stderr, "du: "); 442 perror(base); 443 } 444 exitdu(1); 445 } 446 } 447 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) { 448 if (rflg) { 449 (void) fprintf(stderr, "du: "); 450 perror(base); 451 } 452 *retcode = 1; 453 *ebase0 = 0; 454 (void) closedir(dirp); 455 dirp = NULL; 456 level--; 457 return (0); 458 } 459 while (dp = readdir(dirp)) { 460 if ((strcmp(dp->d_name, ".") == 0) || 461 (strcmp(dp->d_name, "..") == 0)) 462 continue; 463 /* 464 * we're about to append "/" + dp->d_name 465 * onto end of base; make sure there's enough 466 * space 467 */ 468 while ((offset + strlen(dp->d_name) + 2) > base_len) { 469 base_len = base_len * 2; 470 if ((base = (char *)realloc(base, 471 base_len * sizeof (char))) == NULL) { 472 if (rflg) { 473 perror("du"); 474 } 475 exitdu(1); 476 } 477 ebase = base + offset; 478 ebase0 = base + offset0; 479 } 480 /* LINTED - unbounded string specifier */ 481 (void) sprintf(ebase, "/%s", dp->d_name); 482 curoff = telldir(dirp); 483 retval = descend(ebase + 1, 0, retcode, device); 484 /* base may have been moved via realloc in descend() */ 485 ebase = base + offset; 486 ebase0 = base + offset0; 487 *ebase = 0; 488 blocks += retval; 489 if (dirp == NULL) { 490 if ((dirp = opendir(".")) == NULL) { 491 if (rflg) { 492 (void) fprintf(stderr, 493 gettext("du: Can't reopen in ")); 494 perror(base); 495 } 496 *retcode = 1; 497 level--; 498 return (0); 499 } 500 seekdir(dirp, curoff); 501 } 502 } 503 (void) closedir(dirp); 504 level--; 505 dirp = NULL; 506 if (sflg == 0) 507 printsize(blocks, base); 508 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) 509 ret = chdir(dirbuf); 510 else 511 ret = chdir(".."); 512 if (ret < 0) { 513 if (rflg) { 514 (void) sprintf(strchr(base, '\0'), "/.."); 515 (void) fprintf(stderr, 516 gettext("du: Can't change dir to '..' in ")); 517 perror(base); 518 } 519 exitdu(1); 520 } 521 *ebase0 = 0; 522 if (oflg) 523 return (0); 524 else 525 return (blocks); 526 } 527 528 static u_longlong_t 529 mkb(blkcnt_t n, size_t shift) 530 { 531 u_longlong_t v = (u_longlong_t)n; 532 533 /* 534 * If hflg was not used, we need to output number of blocks 535 * rounded up. Block sizes can be 1M, 1K or 512 bytes. 536 * First, convert blocks to 1 byte units and then round up. 537 */ 538 if (!Aflg) 539 v <<= DEV_BSHIFT; 540 541 return (P2ROUNDUP(v, 1 << shift) >> shift); 542 } 543 544 static void 545 printsize(blkcnt_t blocks, char *path) 546 { 547 if (hflg) { 548 u_longlong_t bsize = Aflg ? 1 : (1 << DEV_BSHIFT); 549 550 char buf[NN_NUMBUF_SZ] = { 0 }; 551 552 nicenum_scale(blocks, bsize, buf, sizeof (buf), 0); 553 (void) printf(FORMAT1, buf, path); 554 return; 555 } 556 557 if (kflg) { 558 (void) printf(FORMAT2, mkb(blocks, DEV_KSHIFT), path); 559 } else if (mflg) { 560 (void) printf(FORMAT2, mkb(blocks, DEV_MSHIFT), path); 561 } else { 562 (void) printf(FORMAT2, mkb(blocks, DEV_BSHIFT), path); 563 } 564 } 565 566 static void 567 exitdu(int exitcode) 568 { 569 free(base); 570 free(name); 571 exit(exitcode); 572 } 573