xref: /illumos-gate/usr/src/cmd/du/du.c (revision 4eaa471005973e11a6110b69fe990530b3b95a38)
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