xref: /illumos-gate/usr/src/cmd/du/du.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 /*
34  * du -- summarize disk usage
35  *	/bin/du [-a][-d][-h|-k][-H|-L][-r][-o|-s] [file ...]
36  *	/usr/xpg4/bin/du [-a][-h|-k][-H|-L][-r][-s][-x] [file ...]
37  */
38 
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/avl.h>
42 #include <fcntl.h>
43 #include <dirent.h>
44 #include <limits.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <locale.h>
50 #include <libcmdutils.h>
51 
52 
53 static int		aflg = 0;
54 static int		rflg = 0;
55 static int		sflg = 0;
56 static int		kflg = 0;
57 static int		oflg = 0;
58 static int		dflg = 0;
59 static int		hflg = 0;
60 static int		Hflg = 0;
61 static int		Lflg = 0;
62 static int		cmdarg = 0;	/* Command line argument */
63 static char		*dot = ".";
64 static int		level = 0;	/* Level of recursion */
65 
66 static char		*base;
67 static char		*name;
68 static size_t		base_len = PATH_MAX + 1;    /* # of chars for base */
69 static size_t		name_len = PATH_MAX + 1;    /* # of chars for name */
70 
71 #define	NUMBER_WIDTH	64
72 typedef char		numbuf_t[NUMBER_WIDTH];
73 
74 /*
75  * convert DEV_BSIZE blocks to K blocks
76  */
77 #define	DEV_BSIZE	512
78 #define	DEV_KSHIFT	1
79 #define	kb(n)		(((u_longlong_t)(n)) >> DEV_KSHIFT)
80 
81 long	wait();
82 static u_longlong_t 	descend(char *curname, int curfd, int *retcode,
83 			    dev_t device);
84 static void		printsize(blkcnt_t blocks, char *path);
85 static void		exitdu(int exitcode);
86 
87 static avl_tree_t	*tree = NULL;
88 
89 int
90 main(int argc, char **argv)
91 {
92 	blkcnt_t	blocks = 0;
93 	int		c;
94 	extern int	optind;
95 	char		*np;
96 	pid_t		pid, wpid;
97 	int		status, retcode = 0;
98 	setbuf(stderr, NULL);
99 	(void) setlocale(LC_ALL, "");
100 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
101 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
102 #endif
103 	(void) textdomain(TEXT_DOMAIN);
104 
105 #ifdef XPG4
106 	rflg++;		/* "-r" is not an option but ON always */
107 #endif
108 
109 #ifdef XPG4
110 	while ((c = getopt(argc, argv, "ahHkLrsx")) != EOF)
111 #else
112 	while ((c = getopt(argc, argv, "adhHkLors")) != EOF)
113 #endif
114 		switch (c) {
115 
116 		case 'a':
117 			aflg++;
118 			continue;
119 
120 		case 'h':
121 			hflg++;
122 			continue;
123 
124 		case 'r':
125 			rflg++;
126 			continue;
127 
128 		case 's':
129 			sflg++;
130 			continue;
131 
132 		case 'k':
133 			kflg++;
134 			continue;
135 
136 		case 'o':
137 			oflg++;
138 			continue;
139 
140 		case 'd':
141 			dflg++;
142 			continue;
143 
144 		case 'x':
145 			dflg++;
146 			continue;
147 
148 		case 'H':
149 			Hflg++;
150 			/* -H and -L are mutually exclusive */
151 			Lflg = 0;
152 			cmdarg++;
153 			continue;
154 
155 		case 'L':
156 			Lflg++;
157 			/* -H and -L are mutually exclusive */
158 			Hflg = 0;
159 			cmdarg = 0;
160 			continue;
161 #ifdef XPG4
162 		case '?':
163 			(void) fprintf(stderr, gettext(
164 			    "usage: du [-a] [-h|-k] [-r] [-s] [-x] [-H|-L]"
165 			    " [file ...]\n"));
166 			exit(2);
167 #else
168 		case '?':
169 			(void) fprintf(stderr, gettext(
170 			    "usage: du [-a] [-d] [-h|-k] [-r] [-o|-s] [-H|-L]"
171 			    " [file ...]\n"));
172 			exit(2);
173 #endif
174 		}
175 	if (optind == argc) {
176 		argv = &dot;
177 		argc = 1;
178 		optind = 0;
179 	}
180 
181 	/* "-o" and "-s" don't make any sense together. */
182 	if (oflg && sflg)
183 		oflg = 0;
184 
185 	if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) {
186 		perror("du");
187 		exit(1);
188 	}
189 	if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) {
190 		perror("du");
191 		free(base);
192 		exit(1);
193 	}
194 	do {
195 		if (optind < argc - 1) {
196 			pid = fork();
197 			if (pid == (pid_t)-1) {
198 				perror(gettext("du: No more processes"));
199 				exitdu(1);
200 			}
201 			if (pid != 0) {
202 				while ((wpid = wait(&status)) != pid &&
203 				    wpid != (pid_t)-1)
204 					;
205 				if (pid != (pid_t)-1 && status != 0)
206 					retcode = 1;
207 			}
208 		}
209 		if (optind == argc - 1 || pid == 0) {
210 			while (base_len < (strlen(argv[optind]) + 1)) {
211 				base_len = base_len * 2;
212 				if ((base = (char *)realloc(base, base_len *
213 				    sizeof (char))) == NULL) {
214 					if (rflg) {
215 						(void) fprintf(stderr, gettext(
216 						    "du: can't process %s"),
217 						    argv[optind]);
218 						perror("");
219 					}
220 					exitdu(1);
221 				}
222 			}
223 			if (base_len > name_len) {
224 				name_len = base_len;
225 				if ((name = (char *)realloc(name, name_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 			(void) strcpy(base, argv[optind]);
237 			(void) strcpy(name, argv[optind]);
238 			if (np = strrchr(name, '/')) {
239 				*np++ = '\0';
240 				if (chdir(*name ? name : "/") < 0) {
241 					if (rflg) {
242 						(void) fprintf(stderr, "du: ");
243 						perror(*name ? name : "/");
244 						exitdu(1);
245 					}
246 					exitdu(0);
247 				}
248 			} else
249 				np = base;
250 			blocks = descend(*np ? np : ".", 0, &retcode,
251 				(dev_t)0);
252 			if (sflg)
253 				printsize(blocks, base);
254 			if (optind < argc - 1)
255 				exitdu(retcode);
256 		}
257 		optind++;
258 	} while (optind < argc);
259 	exitdu(retcode);
260 
261 	return (retcode);
262 }
263 
264 /*
265  * descend recursively, adding up the allocated blocks.
266  * If curname is NULL, curfd is used.
267  */
268 static u_longlong_t
269 descend(char *curname, int curfd, int *retcode, dev_t device)
270 {
271 	static DIR		*dirp = NULL;
272 	char			*ebase0, *ebase;
273 	struct stat		stb, stb1;
274 	int			i, j, ret, fd, tmpflg;
275 	blkcnt_t		blocks = 0;
276 	off_t			curoff = 0;
277 	ptrdiff_t		offset;
278 	ptrdiff_t		offset0;
279 	struct dirent		*dp;
280 	char			dirbuf[PATH_MAX + 1];
281 	u_longlong_t		retval;
282 
283 	ebase0 = ebase = strchr(base, 0);
284 	if (ebase > base && ebase[-1] == '/')
285 		ebase--;
286 	offset = ebase - base;
287 	offset0 = ebase0 - base;
288 
289 	if (curname)
290 		curfd = AT_FDCWD;
291 
292 	/*
293 	 * If neither a -L or a -H was specified, don't follow symlinks.
294 	 * If a -H was specified, don't follow symlinks if the file is
295 	 * not a command line argument.
296 	 */
297 	if (((Lflg == 0) && (Hflg == 0)) || ((Hflg) && !(cmdarg))) {
298 		i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
299 		j = 0;
300 	} else {
301 		i = fstatat(curfd, curname, &stb, 0);
302 		j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
303 
304 		/*
305 		 * Make sure any files encountered while traversing the
306 		 * hierarchy are not considered command line arguments.
307 		 */
308 		if (Hflg) {
309 			cmdarg = 0;
310 		}
311 	}
312 
313 	if ((i < 0) || (j < 0)) {
314 		if (rflg) {
315 			(void) fprintf(stderr, "du: ");
316 			perror(base);
317 		}
318 
319 		/*
320 		 * POSIX states that non-zero status codes are only set
321 		 * when an error message is printed out on stderr
322 		 */
323 		*retcode = (rflg ? 1 : 0);
324 		*ebase0 = 0;
325 		return (0);
326 	}
327 	if (device) {
328 		if (dflg && stb.st_dev != device) {
329 			*ebase0 = 0;
330 			return (0);
331 		}
332 	}
333 	else
334 		device = stb.st_dev;
335 
336 	/*
337 	 * If following links (-L) we need to keep track of all inodes
338 	 * visited so they are only visited/reported once and cycles
339 	 * are avoided.  Otherwise, only keep track of files which are
340 	 * hard links so they only get reported once, and of directories
341 	 * so we don't report a directory and its hierarchy more than
342 	 * once in the special case in which it lies under the
343 	 * hierarchy of a directory which is a hard link.
344 	 * Note:  Files with multiple links should only be counted
345 	 * once.  Since each inode could possibly be referenced by a
346 	 * symbolic link, we need to keep track of all inodes when -L
347 	 * is specified.
348 	 */
349 	if ((Lflg) || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
350 	    (stb.st_nlink > 1)) {
351 		int rc;
352 		if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
353 			if (rc == 0) {
354 				/*
355 				 * This hierarchy, or file with multiple
356 				 * links, has already been visited/reported.
357 				 */
358 				return (0);
359 			} else {
360 				/*
361 				 * An error occurred while trying to add the
362 				 * node to the tree.
363 				 */
364 				if (rflg) {
365 					perror("du");
366 				}
367 				exitdu(1);
368 			}
369 		}
370 	}
371 	blocks = stb.st_blocks;
372 	/*
373 	 * If there are extended attributes on the current file, add their
374 	 * block usage onto the block count.
375 	 */
376 	if (curname && pathconf(curname, _PC_XATTR_EXISTS) == 1) {
377 		if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
378 			if (rflg)
379 				perror(gettext(
380 				    "du: can't access extended attributes"));
381 		}
382 		else
383 		{
384 			tmpflg = sflg;
385 			sflg = 1;
386 			blocks += descend(NULL, fd, retcode, device);
387 			sflg = tmpflg;
388 		}
389 	}
390 	if ((stb.st_mode & S_IFMT) != S_IFDIR) {
391 		/*
392 		 * Don't print twice: if sflg, file will get printed in main().
393 		 * Otherwise, level == 0 means this file is listed on the
394 		 * command line, so print here; aflg means print all files.
395 		 */
396 		if (sflg == 0 && (aflg || level == 0))
397 			printsize(blocks, base);
398 		return (blocks);
399 	}
400 	if (dirp != NULL)
401 		/*
402 		 * Close the parent directory descriptor, we will reopen
403 		 * the directory when we pop up from this level of the
404 		 * recursion.
405 		 */
406 		(void) closedir(dirp);
407 	if (curname == NULL)
408 		dirp = fdopendir(curfd);
409 	else
410 		dirp = opendir(curname);
411 	if (dirp == NULL) {
412 		if (rflg) {
413 			(void) fprintf(stderr, "du: ");
414 			perror(base);
415 		}
416 		*retcode = 1;
417 		*ebase0 = 0;
418 		return (0);
419 	}
420 	level++;
421 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
422 		if (getcwd(dirbuf, PATH_MAX) == NULL) {
423 			if (rflg) {
424 				(void) fprintf(stderr, "du: ");
425 				perror(base);
426 			}
427 			exitdu(1);
428 		}
429 	}
430 	if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
431 		if (rflg) {
432 			(void) fprintf(stderr, "du: ");
433 			perror(base);
434 		}
435 		*retcode = 1;
436 		*ebase0 = 0;
437 		(void) closedir(dirp);
438 		dirp = NULL;
439 		level--;
440 		return (0);
441 	}
442 	while (dp = readdir(dirp)) {
443 		if ((strcmp(dp->d_name, ".") == 0) ||
444 			(strcmp(dp->d_name, "..") == 0))
445 			continue;
446 		/*
447 		 * we're about to append "/" + dp->d_name
448 		 * onto end of base; make sure there's enough
449 		 * space
450 		 */
451 		while ((offset + strlen(dp->d_name) + 2) > base_len) {
452 			base_len = base_len * 2;
453 			if ((base = (char *)realloc(base,
454 			    base_len * sizeof (char))) == NULL) {
455 				if (rflg) {
456 					perror("du");
457 				}
458 				exitdu(1);
459 			}
460 			ebase = base + offset;
461 			ebase0 = base + offset0;
462 		}
463 		/* LINTED - unbounded string specifier */
464 		(void) sprintf(ebase, "/%s", dp->d_name);
465 		curoff = telldir(dirp);
466 		retval = descend(ebase + 1, 0, retcode, device);
467 			/* base may have been moved via realloc in descend() */
468 		ebase = base + offset;
469 		ebase0 = base + offset0;
470 		*ebase = 0;
471 		blocks += retval;
472 		if (dirp == NULL) {
473 			if ((dirp = opendir(".")) == NULL) {
474 				if (rflg) {
475 					(void) fprintf(stderr,
476 					    gettext("du: Can't reopen in "));
477 					perror(base);
478 				}
479 				*retcode = 1;
480 				level--;
481 				return (0);
482 			}
483 			seekdir(dirp, curoff);
484 		}
485 	}
486 	(void) closedir(dirp);
487 	level--;
488 	dirp = NULL;
489 	if (sflg == 0)
490 		printsize(blocks, base);
491 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
492 		ret = chdir(dirbuf);
493 	else
494 		ret = chdir("..");
495 	if (ret < 0) {
496 		if (rflg) {
497 			(void) sprintf(strchr(base, '\0'), "/..");
498 			(void) fprintf(stderr,
499 			    gettext("du: Can't change dir to '..' in "));
500 			perror(base);
501 		}
502 		exitdu(1);
503 	}
504 	*ebase0 = 0;
505 	if (oflg)
506 		return (0);
507 	else
508 		return (blocks);
509 }
510 
511 /*
512  * Convert an unsigned long long to a string representation and place the
513  * result in the caller-supplied buffer.
514  * The given number is in units of "unit_from" size,
515  * this will first be converted to a number in 1024 or 1000 byte size,
516  * depending on the scaling factor.
517  * Then the number is scaled down until it is small enough to be in a good
518  * human readable format i.e. in the range 0 thru scale-1.
519  * If it's smaller than 10 there's room enough to provide one decimal place.
520  * The value "(unsigned long long)-1" is a special case and is always
521  * converted to "-1".
522  * Returns a pointer to the caller-supplied buffer.
523  */
524 static char *
525 number_to_scaled_string(
526 	numbuf_t buf,			/* put the result here */
527 	unsigned long long number,	/* convert this number */
528 	unsigned long long unit_from,	/* number of bytes per input unit */
529 	unsigned long long scale)	/* 1024 (-h)  or 1000 (-H) */
530 {
531 	unsigned long long save = 0;
532 	char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */
533 	char *uom = M;    /* unit of measurement, initially 'K' (=M[0]) */
534 
535 	if ((long long)number == (long long)-1) {
536 		(void) strcpy(buf, "-1");
537 		return (buf);
538 	}
539 
540 	/*
541 	 * Convert number from unit_from to given scale (1024 or 1000)
542 	 * This means multiply number with unit_from and divide by scale.
543 	 * if number is large enough, we first divide and then multiply
544 	 * 	to avoid an overflow
545 	 * 	(large enough here means 100 (rather arbitrary value)
546 	 *	times scale in order to reduce rounding errors)
547 	 * otherwise, we first multiply and then divide
548 	 * 	to avoid an underflow
549 	 */
550 	if (number >= 100L * scale) {
551 		number = number / scale;
552 		number = number * unit_from;
553 	} else {
554 		number = number * unit_from;
555 		number = number / scale;
556 	}
557 
558 	/*
559 	 * Now we have number as a count of scale units.
560 	 * Stop scaling when we reached exa bytes, then something is
561 	 * probably wrong with our number.
562 	 */
563 	while ((number >= scale) && (*uom != 'E')) {
564 		uom++; /* next unit of measurement */
565 		save = number;
566 		number = (number + (scale / 2)) / scale;
567 	}
568 
569 	/* check if we should output a decimal place after the point */
570 	if (save && ((save / scale) < 10)) {
571 		/* sprintf() will round for us */
572 		float fnum = (float)save / scale;
573 		(void) sprintf(buf, "%4.1f%c", fnum, *uom);
574 	} else {
575 		(void) sprintf(buf, "%4llu%c", number, *uom);
576 	}
577 	return (buf);
578 }
579 
580 static void
581 printsize(blkcnt_t blocks, char *path)
582 {
583 	if (hflg) {
584 		numbuf_t numbuf;
585 		unsigned long long scale = 1024L;
586 #ifdef XPG4
587 		(void) printf("%s %s\n",
588 		    number_to_scaled_string(numbuf, blocks, DEV_BSIZE, scale),
589 		    path);
590 #else
591 		(void) printf("%s\t%s\n",
592 		    number_to_scaled_string(numbuf, blocks, DEV_BSIZE, scale),
593 		    path);
594 #endif
595 	} else if (kflg) {
596 #ifdef XPG4
597 		(void) printf("%lld %s\n", (long long)kb(blocks), path);
598 #else
599 		(void) printf("%lld\t%s\n", (long long)kb(blocks), path);
600 #endif
601 	} else {
602 #ifdef XPG4
603 		(void) printf("%lld %s\n", (long long)blocks, path);
604 #else
605 		(void) printf("%lld\t%s\n", (long long)blocks, path);
606 #endif
607 	}
608 }
609 
610 static void
611 exitdu(int exitcode)
612 {
613 	free(base);
614 	free(name);
615 	exit(exitcode);
616 }
617