xref: /illumos-gate/usr/src/cmd/du/du.c (revision 48d6e6ab8fae1f82ef9fbee512e42a4f9d4de74b)
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  * Copyright 2017 OmniTI Computer Consulting, Inc.  All rights reserved.
25  * Copyright 2017 Jason King
26  * Copyright 2025 Edgecast Cloud LLC
27  */
28 
29 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
30 /*	  All Rights Reserved	*/
31 
32 /*
33  * du -- summarize disk usage
34  *	du [-Adorx] [-a|-s] [-h|-k|-m] [-H|-L] [file...]
35  */
36 
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <sys/stat.h>
40 #include <sys/avl.h>
41 #include <sys/sysmacros.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		mflg = 0;
58 static int		oflg = 0;
59 static int		dflg = 0;
60 static int		hflg = 0;
61 static int		Aflg = 0;
62 static int		Hflg = 0;
63 static int		Lflg = 0;
64 static int		cmdarg = 0;	/* Command line argument */
65 static char		*dot = ".";
66 static int		level = 0;	/* Level of recursion */
67 
68 static char		*base;
69 static char		*name;
70 static size_t		base_len = PATH_MAX + 1;    /* # of chars for base */
71 static size_t		name_len = PATH_MAX + 1;    /* # of chars for name */
72 
73 /*
74  * Output formats. illumos uses a tab as separator, XPG4 a space.
75  */
76 #ifdef XPG4
77 #define	FORMAT1	"%s %s\n"
78 #define	FORMAT2	"%llu %s\n"
79 #else
80 #define	FORMAT1	"%s\t%s\n"
81 #define	FORMAT2	"%llu\t%s\n"
82 #endif
83 
84 /*
85  * convert bytes to blocks
86  */
87 #define	DEV_BSHIFT	9
88 #define	DEV_KSHIFT	10
89 #define	DEV_MSHIFT	20
90 
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 		pid = (pid_t)-1;
207 		if (optind < argc - 1) {
208 			pid = fork();
209 			if (pid == (pid_t)-1) {
210 				perror(gettext("du: No more processes"));
211 				exitdu(1);
212 			}
213 			if (pid != 0) {
214 				while ((wpid = wait(&status)) != pid &&
215 				    wpid != (pid_t)-1)
216 					;
217 				if (pid != (pid_t)-1 && status != 0)
218 					retcode = 1;
219 			}
220 		}
221 		if (optind == argc - 1 || pid == 0) {
222 			while (base_len < (strlen(argv[optind]) + 1)) {
223 				base_len = base_len * 2;
224 				if ((base = (char *)realloc(base, base_len *
225 				    sizeof (char))) == NULL) {
226 					if (rflg) {
227 						(void) fprintf(stderr, gettext(
228 						    "du: can't process %s"),
229 						    argv[optind]);
230 						perror("");
231 					}
232 					exitdu(1);
233 				}
234 			}
235 			if (base_len > name_len) {
236 				name_len = base_len;
237 				if ((name = (char *)realloc(name, name_len *
238 				    sizeof (char))) == NULL) {
239 					if (rflg) {
240 						(void) fprintf(stderr, gettext(
241 						    "du: can't process %s"),
242 						    argv[optind]);
243 						perror("");
244 					}
245 					exitdu(1);
246 				}
247 			}
248 			(void) strcpy(base, argv[optind]);
249 			(void) strcpy(name, argv[optind]);
250 			np = strrchr(name, '/');
251 			if (np != NULL) {
252 				*np++ = '\0';
253 				if (chdir(*name ? name : "/") < 0) {
254 					if (rflg) {
255 						(void) fprintf(stderr, "du: ");
256 						perror(*name ? name : "/");
257 						exitdu(1);
258 					}
259 					exitdu(0);
260 				}
261 			} else
262 				np = base;
263 			blocks = descend(*np ? np : ".", 0, &retcode,
264 			    (dev_t)0);
265 			if (sflg)
266 				printsize(blocks, base);
267 			if (optind < argc - 1)
268 				exitdu(retcode);
269 		}
270 		optind++;
271 	} while (optind < argc);
272 	exitdu(retcode);
273 
274 	return (retcode);
275 }
276 
277 /*
278  * descend recursively, adding up the allocated blocks.
279  * If curname is NULL, curfd is used.
280  */
281 static u_longlong_t
282 descend(char *curname, int curfd, int *retcode, dev_t device)
283 {
284 	static DIR		*dirp = NULL;
285 	char			*ebase0, *ebase;
286 	struct stat		stb, stb1;
287 	int			i, j, ret, fd, tmpflg;
288 	int			follow_symlinks;
289 	blkcnt_t		blocks = 0;
290 	off_t			curoff = 0;
291 	ptrdiff_t		offset;
292 	ptrdiff_t		offset0;
293 	struct dirent		*dp;
294 	char			dirbuf[PATH_MAX + 1];
295 	u_longlong_t		retval;
296 
297 	ebase0 = ebase = strchr(base, 0);
298 	if (ebase > base && ebase[-1] == '/')
299 		ebase--;
300 	offset = ebase - base;
301 	offset0 = ebase0 - base;
302 
303 	if (curname)
304 		curfd = AT_FDCWD;
305 
306 	/*
307 	 * If neither a -L or a -H was specified, don't follow symlinks.
308 	 * If a -H was specified, don't follow symlinks if the file is
309 	 * not a command line argument.
310 	 */
311 	follow_symlinks = (Lflg || (Hflg && cmdarg));
312 	if (follow_symlinks) {
313 		i = fstatat(curfd, curname, &stb, 0);
314 		j = fstatat(curfd, curname, &stb1, AT_SYMLINK_NOFOLLOW);
315 
316 		/*
317 		 * Make sure any files encountered while traversing the
318 		 * hierarchy are not considered command line arguments.
319 		 */
320 		if (Hflg) {
321 			cmdarg = 0;
322 		}
323 	} else {
324 		i = fstatat(curfd, curname, &stb, AT_SYMLINK_NOFOLLOW);
325 		j = 0;
326 	}
327 
328 	if ((i < 0) || (j < 0)) {
329 		if (rflg) {
330 			(void) fprintf(stderr, "du: ");
331 			perror(base);
332 		}
333 
334 		/*
335 		 * POSIX states that non-zero status codes are only set
336 		 * when an error message is printed out on stderr
337 		 */
338 		*retcode = (rflg ? 1 : 0);
339 		*ebase0 = 0;
340 		return (0);
341 	}
342 	if (device) {
343 		if (dflg && stb.st_dev != device) {
344 			*ebase0 = 0;
345 			return (0);
346 		}
347 	}
348 	else
349 		device = stb.st_dev;
350 
351 	/*
352 	 * If following links (-L) we need to keep track of all inodes
353 	 * visited so they are only visited/reported once and cycles
354 	 * are avoided.  Otherwise, only keep track of files which are
355 	 * hard links so they only get reported once, and of directories
356 	 * so we don't report a directory and its hierarchy more than
357 	 * once in the special case in which it lies under the
358 	 * hierarchy of a directory which is a hard link.
359 	 * Note:  Files with multiple links should only be counted
360 	 * once.  Since each inode could possibly be referenced by a
361 	 * symbolic link, we need to keep track of all inodes when -L
362 	 * is specified.
363 	 */
364 	if (Lflg || ((stb.st_mode & S_IFMT) == S_IFDIR) ||
365 	    (stb.st_nlink > 1)) {
366 		int rc;
367 		if ((rc = add_tnode(&tree, stb.st_dev, stb.st_ino)) != 1) {
368 			if (rc == 0) {
369 				/*
370 				 * This hierarchy, or file with multiple
371 				 * links, has already been visited/reported.
372 				 */
373 				return (0);
374 			} else {
375 				/*
376 				 * An error occurred while trying to add the
377 				 * node to the tree.
378 				 */
379 				if (rflg) {
380 					perror("du");
381 				}
382 				exitdu(1);
383 			}
384 		}
385 	}
386 	blocks = Aflg ? stb.st_size : stb.st_blocks;
387 
388 	/*
389 	 * If there are extended attributes on the current file, add their
390 	 * block usage onto the block count.  Note: Since pathconf() always
391 	 * follows symlinks, only test for extended attributes using pathconf()
392 	 * if we are following symlinks or the current file is not a symlink.
393 	 */
394 	if (curname && (follow_symlinks ||
395 	    ((stb.st_mode & S_IFMT) != S_IFLNK)) &&
396 	    pathconf(curname, _PC_XATTR_EXISTS) == 1) {
397 		if ((fd = attropen(curname, ".", O_RDONLY)) < 0) {
398 			if (rflg)
399 				perror(gettext(
400 				    "du: can't access extended attributes"));
401 		}
402 		else
403 		{
404 			tmpflg = sflg;
405 			sflg = 1;
406 			blocks += descend(NULL, fd, retcode, device);
407 			sflg = tmpflg;
408 		}
409 	}
410 	if ((stb.st_mode & S_IFMT) != S_IFDIR) {
411 		/*
412 		 * Don't print twice: if sflg, file will get printed in main().
413 		 * Otherwise, level == 0 means this file is listed on the
414 		 * command line, so print here; aflg means print all files.
415 		 */
416 		if (sflg == 0 && (aflg || level == 0))
417 			printsize(blocks, base);
418 		return (blocks);
419 	}
420 	if (dirp != NULL)
421 		/*
422 		 * Close the parent directory descriptor, we will reopen
423 		 * the directory when we pop up from this level of the
424 		 * recursion.
425 		 */
426 		(void) closedir(dirp);
427 	if (curname == NULL)
428 		dirp = fdopendir(curfd);
429 	else
430 		dirp = opendir(curname);
431 	if (dirp == NULL) {
432 		if (rflg) {
433 			(void) fprintf(stderr, "du: ");
434 			perror(base);
435 		}
436 		*retcode = 1;
437 		*ebase0 = 0;
438 		return (0);
439 	}
440 	level++;
441 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode))) {
442 		if (getcwd(dirbuf, PATH_MAX) == NULL) {
443 			if (rflg) {
444 				(void) fprintf(stderr, "du: ");
445 				perror(base);
446 			}
447 			exitdu(1);
448 		}
449 	}
450 	if ((curname ? (chdir(curname) < 0) : (fchdir(curfd) < 0))) {
451 		if (rflg) {
452 			(void) fprintf(stderr, "du: ");
453 			perror(base);
454 		}
455 		*retcode = 1;
456 		*ebase0 = 0;
457 		(void) closedir(dirp);
458 		dirp = NULL;
459 		level--;
460 		return (0);
461 	}
462 	while ((dp = readdir(dirp)) != NULL) {
463 		if ((strcmp(dp->d_name, ".") == 0) ||
464 		    (strcmp(dp->d_name, "..") == 0))
465 			continue;
466 		/*
467 		 * we're about to append "/" + dp->d_name
468 		 * onto end of base; make sure there's enough
469 		 * space
470 		 */
471 		while ((offset + strlen(dp->d_name) + 2) > base_len) {
472 			base_len = base_len * 2;
473 			if ((base = (char *)realloc(base,
474 			    base_len * sizeof (char))) == NULL) {
475 				if (rflg) {
476 					perror("du");
477 				}
478 				exitdu(1);
479 			}
480 			ebase = base + offset;
481 			ebase0 = base + offset0;
482 		}
483 		/* LINTED - unbounded string specifier */
484 		(void) sprintf(ebase, "/%s", dp->d_name);
485 		curoff = telldir(dirp);
486 		retval = descend(ebase + 1, 0, retcode, device);
487 			/* base may have been moved via realloc in descend() */
488 		ebase = base + offset;
489 		ebase0 = base + offset0;
490 		*ebase = 0;
491 		blocks += retval;
492 		if (dirp == NULL) {
493 			if ((dirp = opendir(".")) == NULL) {
494 				if (rflg) {
495 					(void) fprintf(stderr,
496 					    gettext("du: Can't reopen in "));
497 					perror(base);
498 				}
499 				*retcode = 1;
500 				level--;
501 				return (0);
502 			}
503 			seekdir(dirp, curoff);
504 		}
505 	}
506 	(void) closedir(dirp);
507 	level--;
508 	dirp = NULL;
509 	if (sflg == 0)
510 		printsize(blocks, base);
511 	if (curname == NULL || (Lflg && S_ISLNK(stb1.st_mode)))
512 		ret = chdir(dirbuf);
513 	else
514 		ret = chdir("..");
515 	if (ret < 0) {
516 		if (rflg) {
517 			(void) sprintf(strchr(base, '\0'), "/..");
518 			(void) fprintf(stderr,
519 			    gettext("du: Can't change dir to '..' in "));
520 			perror(base);
521 		}
522 		exitdu(1);
523 	}
524 	*ebase0 = 0;
525 	if (oflg)
526 		return (0);
527 	else
528 		return (blocks);
529 }
530 
531 static u_longlong_t
532 mkb(blkcnt_t n, size_t shift)
533 {
534 	u_longlong_t v = (u_longlong_t)n;
535 
536 	/*
537 	 * If hflg was not used, we need to output number of blocks
538 	 * rounded up. Block sizes can be 1M, 1K or 512 bytes.
539 	 * First, convert blocks to 1 byte units and then round up.
540 	 */
541 	if (!Aflg)
542 		v <<= DEV_BSHIFT;
543 
544 	return (P2ROUNDUP(v, 1 << shift) >> shift);
545 }
546 
547 static void
548 printsize(blkcnt_t blocks, char *path)
549 {
550 	if (hflg) {
551 		u_longlong_t bsize = Aflg ? 1 : (1 << DEV_BSHIFT);
552 
553 		char buf[NN_NUMBUF_SZ] = { 0 };
554 
555 		nicenum_scale(blocks, bsize, buf, sizeof (buf), 0);
556 		(void) printf(FORMAT1, buf, path);
557 		return;
558 	}
559 
560 	if (kflg) {
561 		(void) printf(FORMAT2, mkb(blocks, DEV_KSHIFT), path);
562 	} else if (mflg) {
563 		(void) printf(FORMAT2, mkb(blocks, DEV_MSHIFT), path);
564 	} else {
565 		(void) printf(FORMAT2, mkb(blocks, DEV_BSHIFT), path);
566 	}
567 }
568 
569 static void
570 exitdu(int exitcode)
571 {
572 	free(base);
573 	free(name);
574 	exit(exitcode);
575 }
576