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
50static int		aflg = 0;
51static int		rflg = 0;
52static int		sflg = 0;
53static int		kflg = 0;
54static int		mflg = 0;
55static int		oflg = 0;
56static int		dflg = 0;
57static int		hflg = 0;
58static int		Aflg = 0;
59static int		Hflg = 0;
60static int		Lflg = 0;
61static int		cmdarg = 0;	/* Command line argument */
62static char		*dot = ".";
63static int		level = 0;	/* Level of recursion */
64
65static char		*base;
66static char		*name;
67static size_t		base_len = PATH_MAX + 1;    /* # of chars for base */
68static 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
90long	wait();
91static u_longlong_t 	descend(char *curname, int curfd, int *retcode,
92			    dev_t device);
93static void		printsize(blkcnt_t blocks, char *path);
94static void		exitdu(int exitcode);
95
96static avl_tree_t	*tree = NULL;
97
98int
99main(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 */
279static u_longlong_t
280descend(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
529static void
530printsize(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
550static void
551exitdu(int exitcode)
552{
553	free(base);
554	free(name);
555	exit(exitcode);
556}
557