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