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
50static int		aflg = 0;
51static int		rflg = 0;
52static int		sflg = 0;
53static int		kflg = 0;
54static int		mflg = 0;
55static int		oflg = 0;
56static int		dflg = 0;
57static int		hflg = 0;
58static int		Hflg = 0;
59static int		Lflg = 0;
60static int		cmdarg = 0;	/* Command line argument */
61static char		*dot = ".";
62static int		level = 0;	/* Level of recursion */
63
64static char		*base;
65static char		*name;
66static size_t		base_len = PATH_MAX + 1;    /* # of chars for base */
67static size_t		name_len = PATH_MAX + 1;    /* # of chars for name */
68
69#define	NUMBER_WIDTH	64
70typedef 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
92long	wait();
93static u_longlong_t 	descend(char *curname, int curfd, int *retcode,
94			    dev_t device);
95static void		printsize(blkcnt_t blocks, char *path);
96static void		exitdu(int exitcode);
97
98static avl_tree_t	*tree = NULL;
99
100int
101main(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 */
277static u_longlong_t
278descend(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 */
539static char *
540number_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
595static void
596printsize(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
613static void
614exitdu(int exitcode)
615{
616	free(base);
617	free(name);
618	exit(exitcode);
619}
620