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  */
  26 
  27 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T     */
  28 /*        All Rights Reserved   */
  29 
  30 /*
  31  * du -- summarize disk usage
  32  *      du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...]
  33  */
  34 
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 #include <sys/avl.h>
  38 #include <fcntl.h>
  39 #include <dirent.h>
  40 #include <limits.h>
  41 #include <stdio.h>
  42 #include <stdlib.h>
  43 #include <string.h>
  44 #include <unistd.h>
  45 #include <locale.h>
  46 #include <libcmdutils.h>
  47 
  48 
  49 static int              aflg = 0;
  50 static int              rflg = 0;
  51 static int              sflg = 0;
  52 static int              kflg = 0;
  53 static int              mflg = 0;
  54 static int              oflg = 0;
  55 static int              dflg = 0;
  56 static int              hflg = 0;
  57 static int              Aflg = 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. illumos 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, "aAdhHkLmorsx")) != 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 'A':
 166                         Aflg++;
 167                         continue;
 168 
 169                 case 'H':
 170                         Hflg++;
 171                         /* -H and -L are mutually exclusive */
 172                         Lflg = 0;
 173                         cmdarg++;
 174                         continue;
 175 
 176                 case 'L':
 177                         Lflg++;
 178                         /* -H and -L are mutually exclusive */
 179                         Hflg = 0;
 180                         cmdarg = 0;
 181                         continue;
 182                 case '?':
 183                         (void) fprintf(stderr, gettext(
 184                             "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] "
 185                             "[file...]\n"));
 186                         exit(2);
 187                 }
 188         if (optind == argc) {
 189                 argv = &dot;
 190                 argc = 1;
 191                 optind = 0;
 192         }
 193 
 194         /* "-o" and "-s" don't make any sense together. */
 195         if (oflg && sflg)
 196                 oflg = 0;
 197 
 198         if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) {
 199                 perror("du");
 200                 exit(1);
 201         }
 202         if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) {
 203                 perror("du");
 204                 free(base);
 205                 exit(1);
 206         }
 207         do {
 208                 if (optind < argc - 1) {
 209                         pid = fork();
 210                         if (pid == (pid_t)-1) {
 211                                 perror(gettext("du: No more processes"));
 212                                 exitdu(1);
 213                         }
 214                         if (pid != 0) {
 215                                 while ((wpid = wait(&status)) != pid &&
 216                                     wpid != (pid_t)-1)
 217                                         ;
 218                                 if (pid != (pid_t)-1 && status != 0)
 219                                         retcode = 1;
 220                         }
 221                 }
 222                 if (optind == argc - 1 || pid == 0) {
 223                         while (base_len < (strlen(argv[optind]) + 1)) {
 224                                 base_len = base_len * 2;
 225                                 if ((base = (char *)realloc(base, base_len *
 226                                     sizeof (char))) == NULL) {
 227                                         if (rflg) {
 228                                                 (void) fprintf(stderr, gettext(
 229                                                     "du: can't process %s"),
 230                                                     argv[optind]);
 231                                                 perror("");
 232                                         }
 233                                         exitdu(1);
 234                                 }
 235                         }
 236                         if (base_len > name_len) {
 237                                 name_len = base_len;
 238                                 if ((name = (char *)realloc(name, name_len *
 239                                     sizeof (char))) == NULL) {
 240                                         if (rflg) {
 241                                                 (void) fprintf(stderr, gettext(
 242                                                     "du: can't process %s"),
 243                                                     argv[optind]);
 244                                                 perror("");
 245                                         }
 246                                         exitdu(1);
 247                                 }
 248                         }
 249                         (void) strcpy(base, argv[optind]);
 250                         (void) strcpy(name, argv[optind]);
 251                         if (np = strrchr(name, '/')) {
 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)) {
 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 /*
 532  * Convert an unsigned long long to a string representation and place the
 533  * result in the caller-supplied buffer.
 534  * The given number is in units of "unit_from" size,
 535  * this will first be converted to a number in 1024 or 1000 byte size,
 536  * depending on the scaling factor.
 537  * Then the number is scaled down until it is small enough to be in a good
 538  * human readable format i.e. in the range 0 thru scale-1.
 539  * If it's smaller than 10 there's room enough to provide one decimal place.
 540  * The value "(unsigned long long)-1" is a special case and is always
 541  * converted to "-1".
 542  * Returns a pointer to the caller-supplied buffer.
 543  */
 544 static char *
 545 number_to_scaled_string(
 546         numbuf_t buf,                   /* put the result here */
 547         unsigned long long number,      /* convert this number */
 548         unsigned long long unit_from,   /* number of bytes per input unit */
 549         unsigned long long scale)       /* 1024 (-h)  or 1000 (-H) */
 550 {
 551         unsigned long long save = 0;
 552         char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
 553         char *uom = M;    /* unit of measurement, initially 'K' (=M[0]) */
 554 
 555         if ((long long)number == (long long)-1) {
 556                 (void) strcpy(buf, "-1");
 557                 return (buf);
 558         }
 559 
 560         /*
 561          * Convert number from unit_from to given scale (1024 or 1000)
 562          * This means multiply number with unit_from and divide by scale.
 563          * if number is large enough, we first divide and then multiply
 564          *      to avoid an overflow
 565          *      (large enough here means 100 (rather arbitrary value)
 566          *      times scale in order to reduce rounding errors)
 567          * otherwise, we first multiply and then divide
 568          *      to avoid an underflow
 569          */
 570         if (number >= 100L * scale) {
 571                 number = number / scale;
 572                 number = number * unit_from;
 573         } else {
 574                 number = number * unit_from;
 575                 number = number / scale;
 576         }
 577 
 578         /*
 579          * Now we have number as a count of scale units.
 580          * Stop scaling when we reached exa bytes, then something is
 581          * probably wrong with our number.
 582          */
 583         while ((number >= scale) && (*uom != 'E')) {
 584                 uom++; /* next unit of measurement */
 585                 save = number;
 586                 number = (number + (scale / 2)) / scale;
 587         }
 588 
 589         /* check if we should output a decimal place after the point */
 590         if (save && ((save / scale) < 10)) {
 591                 /* sprintf() will round for us */
 592                 float fnum = (float)save / scale;
 593                 (void) sprintf(buf, "%4.1f%c", fnum, *uom);
 594         } else {
 595                 (void) sprintf(buf, "%4llu%c", number, *uom);
 596         }
 597         return (buf);
 598 }
 599 
 600 static void
 601 printsize(blkcnt_t blocks, char *path)
 602 {
 603         u_longlong_t bsize;
 604 
 605         bsize = Aflg ? 1 : DEV_BSIZE;
 606 
 607         if (hflg) {
 608                 numbuf_t numbuf;
 609                 unsigned long long scale = 1024L;
 610                 (void) printf(FORMAT1,
 611                     number_to_scaled_string(numbuf, blocks, bsize, scale),
 612                     path);
 613         } else if (kflg) {
 614                 (void) printf(FORMAT2, (long long)kb(blocks), path);
 615         } else if (mflg) {
 616                 (void) printf(FORMAT2, (long long)mb(blocks), path);
 617         } else {
 618                 (void) printf(FORMAT2, (long long)blocks, path);
 619         }
 620 }
 621 
 622 static void
 623 exitdu(int exitcode)
 624 {
 625         free(base);
 626         free(name);
 627         exit(exitcode);
 628 }