xref: /illumos-gate/usr/src/cmd/du/du.c (revision dea726cae202a33907cb28ad7d4b689a984b4d9b)
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 2017 OmniTI Computer Consulting, Inc.  All rights reserved.
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2017 Jason King
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved	*/
30 
31 /*
32  * du -- summarize disk usage
33  *	du [-Adorx] [-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 <sys/sysmacros.h>
40 #include <fcntl.h>
41 #include <dirent.h>
42 #include <limits.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <locale.h>
48 #include <libcmdutils.h>
49 
50 
51 static int		aflg = 0;
52 static int		rflg = 0;
53 static int		sflg = 0;
54 static int		kflg = 0;
55 static int		mflg = 0;
56 static int		oflg = 0;
57 static int		dflg = 0;
58 static int		hflg = 0;
59 static int		Aflg = 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 /*
72  * Output formats. illumos uses a tab as separator, XPG4 a space.
73  */
74 #ifdef XPG4
75 #define	FORMAT1	"%s %s\n"
76 #define	FORMAT2	"%llu %s\n"
77 #else
78 #define	FORMAT1	"%s\t%s\n"
79 #define	FORMAT2	"%llu\t%s\n"
80 #endif
81 
82 /*
83  * convert bytes to blocks
84  */
85 #define	DEV_BSHIFT	9
86 #define	DEV_KSHIFT	10
87 #define	DEV_MSHIFT	20
88 
89 long	wait();
90 static u_longlong_t	descend(char *curname, int curfd, int *retcode,
91 			    dev_t device);
92 static void		printsize(blkcnt_t blocks, char *path);
93 static void		exitdu(int exitcode);
94 
95 static avl_tree_t	*tree = NULL;
96 
97 int
98 main(int argc, char **argv)
99 {
100 	blkcnt_t	blocks = 0;
101 	int		c;
102 	extern int	optind;
103 	char		*np;
104 	pid_t		pid, wpid;
105 	int		status, retcode = 0;
106 	setbuf(stderr, NULL);
107 	(void) setlocale(LC_ALL, "");
108 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
109 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it weren't */
110 #endif
111 	(void) textdomain(TEXT_DOMAIN);
112 
113 #ifdef XPG4
114 	rflg++;		/* "-r" is not an option but ON always */
115 #endif
116 
117 	while ((c = getopt(argc, argv, "aAdhHkLmorsx")) != EOF)
118 		switch (c) {
119 
120 		case 'a':
121 			aflg++;
122 			continue;
123 
124 		case 'h':
125 			hflg++;
126 			kflg = 0;
127 			mflg = 0;
128 			continue;
129 
130 		case 'r':
131 			rflg++;
132 			continue;
133 
134 		case 's':
135 			sflg++;
136 			continue;
137 
138 		case 'k':
139 			kflg++;
140 			hflg = 0;
141 			mflg = 0;
142 			continue;
143 
144 		case 'm':
145 			mflg++;
146 			hflg = 0;
147 			kflg = 0;
148 			continue;
149 
150 		case 'o':
151 			oflg++;
152 			continue;
153 
154 		case 'd':
155 			dflg++;
156 			continue;
157 
158 		case 'x':
159 			dflg++;
160 			continue;
161 
162 		case 'A':
163 			Aflg++;
164 			continue;
165 
166 		case 'H':
167 			Hflg++;
168 			/* -H and -L are mutually exclusive */
169 			Lflg = 0;
170 			cmdarg++;
171 			continue;
172 
173 		case 'L':
174 			Lflg++;
175 			/* -H and -L are mutually exclusive */
176 			Hflg = 0;
177 			cmdarg = 0;
178 			continue;
179 		case '?':
180 			(void) fprintf(stderr, gettext(
181 			    "usage: du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] "
182 			    "[file...]\n"));
183 			exit(2);
184 		}
185 	if (optind == argc) {
186 		argv = &dot;
187 		argc = 1;
188 		optind = 0;
189 	}
190 
191 	/* "-o" and "-s" don't make any sense together. */
192 	if (oflg && sflg)
193 		oflg = 0;
194 
195 	if ((base = (char *)calloc(base_len, sizeof (char))) == NULL) {
196 		perror("du");
197 		exit(1);
198 	}
199 	if ((name = (char *)calloc(name_len, sizeof (char))) == NULL) {
200 		perror("du");
201 		free(base);
202 		exit(1);
203 	}
204 	do {
205 		if (optind < argc - 1) {
206 			pid = fork();
207 			if (pid == (pid_t)-1) {
208 				perror(gettext("du: No more processes"));
209 				exitdu(1);
210 			}
211 			if (pid != 0) {
212 				while ((wpid = wait(&status)) != pid &&
213 				    wpid != (pid_t)-1)
214 					;
215 				if (pid != (pid_t)-1 && status != 0)
216 					retcode = 1;
217 			}
218 		}
219 		if (optind == argc - 1 || pid == 0) {
220 			while (base_len < (strlen(argv[optind]) + 1)) {
221 				base_len = base_len * 2;
222 				if ((base = (char *)realloc(base, base_len *
223 				    sizeof (char))) == NULL) {
224 					if (rflg) {
225 						(void) fprintf(stderr, gettext(
226 						    "du: can't process %s"),
227 						    argv[optind]);
228 						perror("");
229 					}
230 					exitdu(1);
231 				}
232 			}
233 			if (base_len > name_len) {
234 				name_len = base_len;
235 				if ((name = (char *)realloc(name, name_len *
236 				    sizeof (char))) == NULL) {
237 					if (rflg) {
238 						(void) fprintf(stderr, gettext(
239 						    "du: can't process %s"),
240 						    argv[optind]);
241 						perror("");
242 					}
243 					exitdu(1);
244 				}
245 			}
246 			(void) strcpy(base, argv[optind]);
247 			(void) strcpy(name, argv[optind]);
248 			if (np = strrchr(name, '/')) {
249 				*np++ = '\0';
250 				if (chdir(*name ? name : "/") < 0) {
251 					if (rflg) {
252 						(void) fprintf(stderr, "du: ");
253 						perror(*name ? name : "/");
254 						exitdu(1);
255 					}
256 					exitdu(0);
257 				}
258 			} else
259 				np = base;
260 			blocks = descend(*np ? np : ".", 0, &retcode,
261 			    (dev_t)0);
262 			if (sflg)
263 				printsize(blocks, base);
264 			if (optind < argc - 1)
265 				exitdu(retcode);
266 		}
267 		optind++;
268 	} while (optind < argc);
269 	exitdu(retcode);
270 
271 	return (retcode);
272 }
273 
274 /*
275  * descend recursively, adding up the allocated blocks.
276  * If curname is NULL, curfd is used.
277  */
278 static u_longlong_t
279 descend(char *curname, int curfd, int *retcode, dev_t device)
280 {
281 	static DIR		*dirp = NULL;
282 	char			*ebase0, *ebase;
283 	struct stat		stb, stb1;
284 	int			i, j, ret, fd, tmpflg;
285 	int			follow_symlinks;
286 	blkcnt_t		blocks = 0;
287 	off_t			curoff = 0;
288 	ptrdiff_t		offset;
289 	ptrdiff_t		offset0;
290 	struct dirent		*dp;
291 	char			dirbuf[PATH_MAX + 1];
292 	u_longlong_t		retval;
293 
294 	ebase0 = ebase = strchr(base, 0);
295 	if (ebase > base && ebase[-1] == '/')
296 		ebase--;
297 	offset = ebase - base;
298 	offset0 = ebase0 - base;
299 
300 	if (curname)
301 		curfd = AT_FDCWD;
302 
303 	/*
304 	 * If neither a -L or a -H was specified, don't follow symlinks.
305 	 * If a -H was specified, don't follow symlinks if the file is
306 	 * not a command line argument.
307 	 */
308 	follow_symlinks = (Lflg || (Hflg && cmdarg));
309 	if (follow_symlinks) {
310 		i = fstatat(curfd, curname, &stb, 0);
311 		j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
312 
313 		/*
314 		 * Make sure any files encountered while traversing the
315 		 * hierarchy are not considered command line arguments.
316 		 */
317 		if (Hflg) {
318 			cmdarg = 0;
319 		}
320 	} else {
321 		i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
322 		j = 0;
323 	}
324 
325 	if ((i < 0) || (j < 0)) {
326 		if (rflg) {
327 			(void) fprintf(stderr, "du: ");
328 			perror(base);
329 		}
330 
331 		/*
332 		 * POSIX states that non-zero status codes are only set
333 		 * when an error message is printed out on stderr
334 		 */
335 		*retcode = (rflg ? 1 : 0);
336 		*ebase0 = 0;
337 		return (0);
338 	}
339 	if (device) {
340 		if (dflg && stb.st_dev != device) {
341 			*ebase0 = 0;
342 			return (0);
343 		}
344 	}
345 	else
346 		device = stb.st_dev;
347 
348 	/*
349 	 * If following links (-L) we need to keep track of all inodes
350 	 * visited so they are only visited/reported once and cycles
351 	 * are avoided.  Otherwise, only keep track of files which are
352 	 * hard links so they only get reported once, and of directories
353 	 * so we don't report a directory and its hierarchy more than
354 	 * once in the special case in which it lies under the
355 	 * hierarchy of a directory which is a hard link.
356 	 * Note:  Files with multiple links should only be counted
357 	 * once.  Since each inode could possibly be referenced by a
358 	 * symbolic link, we need to keep track of all inodes when -L
359 	 * is specified.
360 	 */
361 	if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
362 	    (stb.st_nlink > 1)) {
363 		int rc;
364 		if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
365 			if (rc == 0) {
366 				/*
367 				 * This hierarchy, or file with multiple
368 				 * links, has already been visited/reported.
369 				 */
370 				return (0);
371 			} else {
372 				/*
373 				 * An error occurred while trying to add the
374 				 * node to the tree.
375 				 */
376 				if (rflg) {
377 					perror("du");
378 				}
379 				exitdu(1);
380 			}
381 		}
382 	}
383 	blocks = Aflg ? stb.st_size : stb.st_blocks;
384 
385 	/*
386 	 * If there are extended attributes on the current file, add their
387 	 * block usage onto the block count.  Note: Since pathconf() always
388 	 * follows symlinks, only test for extended attributes using pathconf()
389 	 * if we are following symlinks or the current file is not a symlink.
390 	 */
391 	if (curname && (follow_symlinks ||
392 	    ((stb.st_mode & S_IFMT) != S_IFLNK)) &&
393 	    pathconf(curname, _PC_XATTR_EXISTS) == 1) {
394 		if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
395 			if (rflg)
396 				perror(gettext(
397 				    "du: can't access extended attributes"));
398 		}
399 		else
400 		{
401 			tmpflg = sflg;
402 			sflg = 1;
403 			blocks += descend(NULL, fd, retcode, device);
404 			sflg = tmpflg;
405 		}
406 	}
407 	if ((stb.st_mode & S_IFMT) != S_IFDIR) {
408 		/*
409 		 * Don't print twice: if sflg, file will get printed in main().
410 		 * Otherwise, level == 0 means this file is listed on the
411 		 * command line, so print here; aflg means print all files.
412 		 */
413 		if (sflg == 0 && (aflg || level == 0))
414 			printsize(blocks, base);
415 		return (blocks);
416 	}
417 	if (dirp != NULL)
418 		/*
419 		 * Close the parent directory descriptor, we will reopen
420 		 * the directory when we pop up from this level of the
421 		 * recursion.
422 		 */
423 		(void) closedir(dirp);
424 	if (curname == NULL)
425 		dirp = fdopendir(curfd);
426 	else
427 		dirp = opendir(curname);
428 	if (dirp == NULL) {
429 		if (rflg) {
430 			(void) fprintf(stderr, "du: ");
431 			perror(base);
432 		}
433 		*retcode = 1;
434 		*ebase0 = 0;
435 		return (0);
436 	}
437 	level++;
438 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
439 		if (getcwd(dirbuf, PATH_MAX) == NULL) {
440 			if (rflg) {
441 				(void) fprintf(stderr, "du: ");
442 				perror(base);
443 			}
444 			exitdu(1);
445 		}
446 	}
447 	if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
448 		if (rflg) {
449 			(void) fprintf(stderr, "du: ");
450 			perror(base);
451 		}
452 		*retcode = 1;
453 		*ebase0 = 0;
454 		(void) closedir(dirp);
455 		dirp = NULL;
456 		level--;
457 		return (0);
458 	}
459 	while (dp = readdir(dirp)) {
460 		if ((strcmp(dp->d_name, ".") == 0) ||
461 		    (strcmp(dp->d_name, "..") == 0))
462 			continue;
463 		/*
464 		 * we're about to append "/" + dp->d_name
465 		 * onto end of base; make sure there's enough
466 		 * space
467 		 */
468 		while ((offset + strlen(dp->d_name) + 2) > base_len) {
469 			base_len = base_len * 2;
470 			if ((base = (char *)realloc(base,
471 			    base_len * sizeof (char))) == NULL) {
472 				if (rflg) {
473 					perror("du");
474 				}
475 				exitdu(1);
476 			}
477 			ebase = base + offset;
478 			ebase0 = base + offset0;
479 		}
480 		/* LINTED - unbounded string specifier */
481 		(void) sprintf(ebase, "/%s", dp->d_name);
482 		curoff = telldir(dirp);
483 		retval = descend(ebase + 1, 0, retcode, device);
484 			/* base may have been moved via realloc in descend() */
485 		ebase = base + offset;
486 		ebase0 = base + offset0;
487 		*ebase = 0;
488 		blocks += retval;
489 		if (dirp == NULL) {
490 			if ((dirp = opendir(".")) == NULL) {
491 				if (rflg) {
492 					(void) fprintf(stderr,
493 					    gettext("du: Can't reopen in "));
494 					perror(base);
495 				}
496 				*retcode = 1;
497 				level--;
498 				return (0);
499 			}
500 			seekdir(dirp, curoff);
501 		}
502 	}
503 	(void) closedir(dirp);
504 	level--;
505 	dirp = NULL;
506 	if (sflg == 0)
507 		printsize(blocks, base);
508 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
509 		ret = chdir(dirbuf);
510 	else
511 		ret = chdir("..");
512 	if (ret < 0) {
513 		if (rflg) {
514 			(void) sprintf(strchr(base, '\0'), "/..");
515 			(void) fprintf(stderr,
516 			    gettext("du: Can't change dir to '..' in "));
517 			perror(base);
518 		}
519 		exitdu(1);
520 	}
521 	*ebase0 = 0;
522 	if (oflg)
523 		return (0);
524 	else
525 		return (blocks);
526 }
527 
528 static u_longlong_t
529 mkb(blkcnt_t n, size_t shift)
530 {
531 	u_longlong_t v = (u_longlong_t)n;
532 
533 	/*
534 	 * If hflg was not used, we need to output number of blocks
535 	 * rounded up. Block sizes can be 1M, 1K or 512 bytes.
536 	 * First, convert blocks to 1 byte units and then round up.
537 	 */
538 	if (!Aflg)
539 		v <<= DEV_BSHIFT;
540 
541 	return (P2ROUNDUP(v, 1 << shift) >> shift);
542 }
543 
544 static void
545 printsize(blkcnt_t blocks, char *path)
546 {
547 	if (hflg) {
548 		u_longlong_t bsize = Aflg ? 1 : (1 << DEV_BSHIFT);
549 
550 		char buf[NN_NUMBUF_SZ] = { 0 };
551 
552 		nicenum_scale(blocks, bsize, buf, sizeof (buf), 0);
553 		(void) printf(FORMAT1, buf, path);
554 		return;
555 	}
556 
557 	if (kflg) {
558 		(void) printf(FORMAT2, mkb(blocks, DEV_KSHIFT), path);
559 	} else if (mflg) {
560 		(void) printf(FORMAT2, mkb(blocks, DEV_MSHIFT), path);
561 	} else {
562 		(void) printf(FORMAT2, mkb(blocks, DEV_BSHIFT), path);
563 	}
564 }
565 
566 static void
567 exitdu(int exitcode)
568 {
569 	free(base);
570 	free(name);
571 	exit(exitcode);
572 }
573