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 * Copyright 2017 OmniTI Computer Consulting, Inc. All rights reserved. 25 * Copyright 2017 Jason King 26 * Copyright 2025 Edgecast Cloud LLC 27 */ 28 29 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 30 /* All Rights Reserved */ 31 32 /* 33 * du -- summarize disk usage 34 * du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...] 35 */ 36 37 #include <sys/types.h> 38 #include <sys/wait.h> 39 #include <sys/stat.h> 40 #include <sys/avl.h> 41 #include <sys/sysmacros.h> 42 #include <fcntl.h> 43 #include <dirent.h> 44 #include <limits.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <locale.h> 50 #include <libcmdutils.h> 51 52 53 static int aflg = 0; 54 static int rflg = 0; 55 static int sflg = 0; 56 static int kflg = 0; 57 static int mflg = 0; 58 static int oflg = 0; 59 static int dflg = 0; 60 static int hflg = 0; 61 static int Aflg = 0; 62 static int Hflg = 0; 63 static int Lflg = 0; 64 static int cmdarg = 0; /* Command line argument */ 65 static char *dot = "."; 66 static int level = 0; /* Level of recursion */ 67 68 static char *base; 69 static char *name; 70 static size_t base_len = PATH_MAX + 1; /* # of chars for base */ 71 static size_t name_len = PATH_MAX + 1; /* # of chars for name */ 72 73 /* 74 * Output formats. illumos uses a tab as separator, XPG4 a space. 75 */ 76 #ifdef XPG4 77 #define FORMAT1 "%s %s\n" 78 #define FORMAT2 "%llu %s\n" 79 #else 80 #define FORMAT1 "%s\t%s\n" 81 #define FORMAT2 "%llu\t%s\n" 82 #endif 83 84 /* 85 * convert bytes to blocks 86 */ 87 #define DEV_BSHIFT 9 88 #define DEV_KSHIFT 10 89 #define DEV_MSHIFT 20 90 91 static u_longlong_t descend(char *curname, int curfd, int *retcode, 92 dev_t device); 93 static void printsize(blkcnt_t blocks, char *path); 94 static void exitdu(int exitcode); 95 96 static avl_tree_t *tree = NULL; 97 98 int 99 main(int argc, char **argv) 100 { 101 blkcnt_t blocks = 0; 102 int c; 103 extern int optind; 104 char *np; 105 pid_t pid, wpid; 106 int status, retcode = 0; 107 setbuf(stderr, NULL); 108 (void) setlocale(LC_ALL, ""); 109 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 110 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 111 #endif 112 (void) textdomain(TEXT_DOMAIN); 113 114 #ifdef XPG4 115 rflg++; /* "-r" is not an option but ON always */ 116 #endif 117 118 while ((c = getopt(argc, argv, "aAdhHkLmorsx")) != EOF) 119 switch (c) { 120 121 case 'a': 122 aflg++; 123 continue; 124 125 case 'h': 126 hflg++; 127 kflg = 0; 128 mflg = 0; 129 continue; 130 131 case 'r': 132 rflg++; 133 continue; 134 135 case 's': 136 sflg++; 137 continue; 138 139 case 'k': 140 kflg++; 141 hflg = 0; 142 mflg = 0; 143 continue; 144 145 case 'm': 146 mflg++; 147 hflg = 0; 148 kflg = 0; 149 continue; 150 151 case 'o': 152 oflg++; 153 continue; 154 155 case 'd': 156 dflg++; 157 continue; 158 159 case 'x': 160 dflg++; 161 continue; 162 163 case 'A': 164 Aflg++; 165 continue; 166 167 case 'H': 168 Hflg++; 169 /* -H and -L are mutually exclusive */ 170 Lflg = 0; 171 cmdarg++; 172 continue; 173 174 case 'L': 175 Lflg++; 176 /* -H and -L are mutually exclusive */ 177 Hflg = 0; 178 cmdarg = 0; 179 continue; 180 case '?': 181 (void) fprintf(stderr, gettext( 182 "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] " 183 "[file...]\n")); 184 exit(2); 185 } 186 if (optind == argc) { 187 argv = ˙ 188 argc = 1; 189 optind = 0; 190 } 191 192 /* "-o" and "-s" don't make any sense together. */ 193 if (oflg && sflg) 194 oflg = 0; 195 196 if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) { 197 perror("du"); 198 exit(1); 199 } 200 if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) { 201 perror("du"); 202 free(base); 203 exit(1); 204 } 205 do { 206 pid = (pid_t)-1; 207 if (optind < argc - 1) { 208 pid = fork(); 209 if (pid == (pid_t)-1) { 210 perror(gettext("du: No more processes")); 211 exitdu(1); 212 } 213 if (pid != 0) { 214 while ((wpid = wait(&status)) != pid && 215 wpid != (pid_t)-1) 216 ; 217 if (pid != (pid_t)-1 && status != 0) 218 retcode = 1; 219 } 220 } 221 if (optind == argc - 1 || pid == 0) { 222 while (base_len < (strlen(argv[optind]) + 1)) { 223 base_len = base_len * 2; 224 if ((base = (char *)realloc(base, base_len * 225 sizeof (char))) == NULL) { 226 if (rflg) { 227 (void) fprintf(stderr, gettext( 228 "du: can't process %s"), 229 argv[optind]); 230 perror(""); 231 } 232 exitdu(1); 233 } 234 } 235 if (base_len > name_len) { 236 name_len = base_len; 237 if ((name = (char *)realloc(name, name_len * 238 sizeof (char))) == NULL) { 239 if (rflg) { 240 (void) fprintf(stderr, gettext( 241 "du: can't process %s"), 242 argv[optind]); 243 perror(""); 244 } 245 exitdu(1); 246 } 247 } 248 (void) strcpy(base, argv[optind]); 249 (void) strcpy(name, argv[optind]); 250 np = strrchr(name, '/'); 251 if (np != NULL) { 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)) != NULL) { 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 static u_longlong_t 532 mkb(blkcnt_t n, size_t shift) 533 { 534 u_longlong_t v = (u_longlong_t)n; 535 536 /* 537 * If hflg was not used, we need to output number of blocks 538 * rounded up. Block sizes can be 1M, 1K or 512 bytes. 539 * First, convert blocks to 1 byte units and then round up. 540 */ 541 if (!Aflg) 542 v <<= DEV_BSHIFT; 543 544 return (P2ROUNDUP(v, 1 << shift) >> shift); 545 } 546 547 static void 548 printsize(blkcnt_t blocks, char *path) 549 { 550 if (hflg) { 551 u_longlong_t bsize = Aflg ? 1 : (1 << DEV_BSHIFT); 552 553 char buf[NN_NUMBUF_SZ] = { 0 }; 554 555 nicenum_scale(blocks, bsize, buf, sizeof (buf), 0); 556 (void) printf(FORMAT1, buf, path); 557 return; 558 } 559 560 if (kflg) { 561 (void) printf(FORMAT2, mkb(blocks, DEV_KSHIFT), path); 562 } else if (mflg) { 563 (void) printf(FORMAT2, mkb(blocks, DEV_MSHIFT), path); 564 } else { 565 (void) printf(FORMAT2, mkb(blocks, DEV_BSHIFT), path); 566 } 567 } 568 569 static void 570 exitdu(int exitcode) 571 { 572 free(base); 573 free(name); 574 exit(exitcode); 575 } 576