OpenGrok

Cross Reference: du.c
xref: /onnv/onnv-gate/usr/src/cmd/du/du.c
Home | History | Annotate | Line # | Download | only in du
      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 }
    620