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