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 <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 Aflg = 0; 59 static int Hflg = 0; 60 static int Lflg = 0; 61 static int cmdarg = 0; /* Command line argument */ 62 static char *dot = "."; 63 static int level = 0; /* Level of recursion */ 64 65 static char *base; 66 static char *name; 67 static size_t base_len = PATH_MAX + 1; /* # of chars for base */ 68 static size_t name_len = PATH_MAX + 1; /* # of chars for name */ 69 70 /* 71 * Output formats. illumos uses a tab as separator, XPG4 a space. 72 */ 73 #ifdef XPG4 74 #define FORMAT1 "%s %s\n" 75 #define FORMAT2 "%lld %s\n" 76 #else 77 #define FORMAT1 "%s\t%s\n" 78 #define FORMAT2 "%lld\t%s\n" 79 #endif 80 81 /* 82 * convert DEV_BSIZE blocks to K blocks 83 */ 84 #define DEV_BSIZE 512 85 #define DEV_KSHIFT 1 86 #define DEV_MSHIFT 11 87 #define kb(n) (((u_longlong_t)(n)) >> DEV_KSHIFT) 88 #define mb(n) (((u_longlong_t)(n)) >> DEV_MSHIFT) 89 90 long wait(); 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 if (optind < argc - 1) { 207 pid = fork(); 208 if (pid == (pid_t)-1) { 209 perror(gettext("du: No more processes")); 210 exitdu(1); 211 } 212 if (pid != 0) { 213 while ((wpid = wait(&status)) != pid && 214 wpid != (pid_t)-1) 215 ; 216 if (pid != (pid_t)-1 && status != 0) 217 retcode = 1; 218 } 219 } 220 if (optind == argc - 1 || pid == 0) { 221 while (base_len < (strlen(argv[optind]) + 1)) { 222 base_len = base_len * 2; 223 if ((base = (char *)realloc(base, base_len * 224 sizeof (char))) == NULL) { 225 if (rflg) { 226 (void) fprintf(stderr, gettext( 227 "du: can't process %s"), 228 argv[optind]); 229 perror(""); 230 } 231 exitdu(1); 232 } 233 } 234 if (base_len > name_len) { 235 name_len = base_len; 236 if ((name = (char *)realloc(name, name_len * 237 sizeof (char))) == NULL) { 238 if (rflg) { 239 (void) fprintf(stderr, gettext( 240 "du: can't process %s"), 241 argv[optind]); 242 perror(""); 243 } 244 exitdu(1); 245 } 246 } 247 (void) strcpy(base, argv[optind]); 248 (void) strcpy(name, argv[optind]); 249 if (np = strrchr(name, '/')) { 250 *np++ = '\0'; 251 if (chdir(*name ? name : "/") < 0) { 252 if (rflg) { 253 (void) fprintf(stderr, "du: "); 254 perror(*name ? name : "/"); 255 exitdu(1); 256 } 257 exitdu(0); 258 } 259 } else 260 np = base; 261 blocks = descend(*np ? np : ".", 0, &retcode, 262 (dev_t)0); 263 if (sflg) 264 printsize(blocks, base); 265 if (optind < argc - 1) 266 exitdu(retcode); 267 } 268 optind++; 269 } while (optind < argc); 270 exitdu(retcode); 271 272 return (retcode); 273 } 274 275 /* 276 * descend recursively, adding up the allocated blocks. 277 * If curname is NULL, curfd is used. 278 */ 279 static u_longlong_t 280 descend(char *curname, int curfd, int *retcode, dev_t device) 281 { 282 static DIR *dirp = NULL; 283 char *ebase0, *ebase; 284 struct stat stb, stb1; 285 int i, j, ret, fd, tmpflg; 286 int follow_symlinks; 287 blkcnt_t blocks = 0; 288 off_t curoff = 0; 289 ptrdiff_t offset; 290 ptrdiff_t offset0; 291 struct dirent *dp; 292 char dirbuf[PATH_MAX + 1]; 293 u_longlong_t retval; 294 295 ebase0 = ebase = strchr(base, 0); 296 if (ebase > base && ebase[-1] == '/') 297 ebase--; 298 offset = ebase - base; 299 offset0 = ebase0 - base; 300 301 if (curname) 302 curfd = AT_FDCWD; 303 304 /* 305 * If neither a -L or a -H was specified, don't follow symlinks. 306 * If a -H was specified, don't follow symlinks if the file is 307 * not a command line argument. 308 */ 309 follow_symlinks = (Lflg || (Hflg && cmdarg)); 310 if (follow_symlinks) { 311 i = fstatat(curfd, curname, &stb, 0); 312 j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW); 313 314 /* 315 * Make sure any files encountered while traversing the 316 * hierarchy are not considered command line arguments. 317 */ 318 if (Hflg) { 319 cmdarg = 0; 320 } 321 } else { 322 i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW); 323 j = 0; 324 } 325 326 if ((i < 0) || (j < 0)) { 327 if (rflg) { 328 (void) fprintf(stderr, "du: "); 329 perror(base); 330 } 331 332 /* 333 * POSIX states that non-zero status codes are only set 334 * when an error message is printed out on stderr 335 */ 336 *retcode = (rflg ? 1 : 0); 337 *ebase0 = 0; 338 return (0); 339 } 340 if (device) { 341 if (dflg && stb.st_dev != device) { 342 *ebase0 = 0; 343 return (0); 344 } 345 } 346 else 347 device = stb.st_dev; 348 349 /* 350 * If following links (-L) we need to keep track of all inodes 351 * visited so they are only visited/reported once and cycles 352 * are avoided. Otherwise, only keep track of files which are 353 * hard links so they only get reported once, and of directories 354 * so we don't report a directory and its hierarchy more than 355 * once in the special case in which it lies under the 356 * hierarchy of a directory which is a hard link. 357 * Note: Files with multiple links should only be counted 358 * once. Since each inode could possibly be referenced by a 359 * symbolic link, we need to keep track of all inodes when -L 360 * is specified. 361 */ 362 if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) || 363 (stb.st_nlink > 1)) { 364 int rc; 365 if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) { 366 if (rc == 0) { 367 /* 368 * This hierarchy, or file with multiple 369 * links, has already been visited/reported. 370 */ 371 return (0); 372 } else { 373 /* 374 * An error occurred while trying to add the 375 * node to the tree. 376 */ 377 if (rflg) { 378 perror("du"); 379 } 380 exitdu(1); 381 } 382 } 383 } 384 blocks = Aflg ? stb.st_size : stb.st_blocks; 385 386 /* 387 * If there are extended attributes on the current file, add their 388 * block usage onto the block count. Note: Since pathconf() always 389 * follows symlinks, only test for extended attributes using pathconf() 390 * if we are following symlinks or the current file is not a symlink. 391 */ 392 if (curname && (follow_symlinks || 393 ((stb.st_mode & S_IFMT) != S_IFLNK)) && 394 pathconf(curname, _PC_XATTR_EXISTS) == 1) { 395 if ((fd = attropen(curname, ".", O_RDONLY)) < 0) { 396 if (rflg) 397 perror(gettext( 398 "du: can't access extended attributes")); 399 } 400 else 401 { 402 tmpflg = sflg; 403 sflg = 1; 404 blocks += descend(NULL, fd, retcode, device); 405 sflg = tmpflg; 406 } 407 } 408 if ((stb.st_mode & S_IFMT) != S_IFDIR) { 409 /* 410 * Don't print twice: if sflg, file will get printed in main(). 411 * Otherwise, level == 0 means this file is listed on the 412 * command line, so print here; aflg means print all files. 413 */ 414 if (sflg == 0 && (aflg || level == 0)) 415 printsize(blocks, base); 416 return (blocks); 417 } 418 if (dirp != NULL) 419 /* 420 * Close the parent directory descriptor, we will reopen 421 * the directory when we pop up from this level of the 422 * recursion. 423 */ 424 (void) closedir(dirp); 425 if (curname == NULL) 426 dirp = fdopendir(curfd); 427 else 428 dirp = opendir(curname); 429 if (dirp == NULL) { 430 if (rflg) { 431 (void) fprintf(stderr, "du: "); 432 perror(base); 433 } 434 *retcode = 1; 435 *ebase0 = 0; 436 return (0); 437 } 438 level++; 439 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) { 440 if (getcwd(dirbuf, PATH_MAX) == NULL) { 441 if (rflg) { 442 (void) fprintf(stderr, "du: "); 443 perror(base); 444 } 445 exitdu(1); 446 } 447 } 448 if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) { 449 if (rflg) { 450 (void) fprintf(stderr, "du: "); 451 perror(base); 452 } 453 *retcode = 1; 454 *ebase0 = 0; 455 (void) closedir(dirp); 456 dirp = NULL; 457 level--; 458 return (0); 459 } 460 while (dp = readdir(dirp)) { 461 if ((strcmp(dp->d_name, ".") == 0) || 462 (strcmp(dp->d_name, "..") == 0)) 463 continue; 464 /* 465 * we're about to append "/" + dp->d_name 466 * onto end of base; make sure there's enough 467 * space 468 */ 469 while ((offset + strlen(dp->d_name) + 2) > base_len) { 470 base_len = base_len * 2; 471 if ((base = (char *)realloc(base, 472 base_len * sizeof (char))) == NULL) { 473 if (rflg) { 474 perror("du"); 475 } 476 exitdu(1); 477 } 478 ebase = base + offset; 479 ebase0 = base + offset0; 480 } 481 /* LINTED - unbounded string specifier */ 482 (void) sprintf(ebase, "/%s", dp->d_name); 483 curoff = telldir(dirp); 484 retval = descend(ebase + 1, 0, retcode, device); 485 /* base may have been moved via realloc in descend() */ 486 ebase = base + offset; 487 ebase0 = base + offset0; 488 *ebase = 0; 489 blocks += retval; 490 if (dirp == NULL) { 491 if ((dirp = opendir(".")) == NULL) { 492 if (rflg) { 493 (void) fprintf(stderr, 494 gettext("du: Can't reopen in ")); 495 perror(base); 496 } 497 *retcode = 1; 498 level--; 499 return (0); 500 } 501 seekdir(dirp, curoff); 502 } 503 } 504 (void) closedir(dirp); 505 level--; 506 dirp = NULL; 507 if (sflg == 0) 508 printsize(blocks, base); 509 if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) 510 ret = chdir(dirbuf); 511 else 512 ret = chdir(".."); 513 if (ret < 0) { 514 if (rflg) { 515 (void) sprintf(strchr(base, '\0'), "/.."); 516 (void) fprintf(stderr, 517 gettext("du: Can't change dir to '..' in ")); 518 perror(base); 519 } 520 exitdu(1); 521 } 522 *ebase0 = 0; 523 if (oflg) 524 return (0); 525 else 526 return (blocks); 527 } 528 529 static void 530 printsize(blkcnt_t blocks, char *path) 531 { 532 u_longlong_t bsize; 533 534 bsize = Aflg ? 1 : DEV_BSIZE; 535 536 if (hflg) { 537 char buf[NN_NUMBUF_SZ] = { 0 }; 538 539 nicenum_scale(blocks, bsize, buf, sizeof (buf), 0); 540 (void) printf(FORMAT1, buf, path); 541 } else if (kflg) { 542 (void) printf(FORMAT2, (long long)kb(blocks), path); 543 } else if (mflg) { 544 (void) printf(FORMAT2, (long long)mb(blocks), path); 545 } else { 546 (void) printf(FORMAT2, (long long)blocks, path); 547 } 548 } 549 550 static void 551 exitdu(int exitcode) 552 { 553 free(base); 554 free(name); 555 exit(exitcode); 556 } 557