1/* vi: set sw=4 ts=4: */
2/*
3 * Mini du implementation for busybox
4 *
5 * Copyright (C) 1999,2000,2001 by Lineo, inc. and John Beppu
6 * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
7 * Copyright (C) 2002  Edward Betts <edward@debian.org>
8 *
9 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
10 */
11
12/* BB_AUDIT SUSv3 compliant (unless default blocksize set to 1k) */
13/* http://www.opengroup.org/onlinepubs/007904975/utilities/du.html */
14
15/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
16 *
17 * Mostly rewritten for SUSv3 compliance and to fix bugs/defects.
18 * 1) Added support for SUSv3 -a, -H, -L, gnu -c, and (busybox) -d options.
19 *    The -d option allows setting of max depth (similar to gnu --max-depth).
20 * 2) Fixed incorrect size calculations for links and directories, especially
21 *    when errors occurred.  Calculates sizes should now match gnu du output.
22 * 3) Added error checking of output.
23 * 4) Fixed busybox bug #1284 involving long overflow with human_readable.
24 */
25
26#include "libbb.h"
27
28#if ENABLE_FEATURE_HUMAN_READABLE
29# if ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
30static unsigned long disp_hr = 1024;
31# else
32static unsigned long disp_hr = 512;
33# endif
34#elif ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
35static unsigned disp_k = 1;
36#else
37static unsigned disp_k;	/* bss inits to 0 */
38#endif
39
40static int max_print_depth = INT_MAX;
41static nlink_t count_hardlinks = 1;
42
43static int status;
44static int print_files;
45static int slink_depth;
46static int du_depth;
47static int one_file_system;
48static dev_t dir_dev;
49
50
51static void print(long size, const char *const filename)
52{
53	/* TODO - May not want to defer error checking here. */
54#if ENABLE_FEATURE_HUMAN_READABLE
55	printf("%s\t%s\n", make_human_readable_str(size, 512, disp_hr),
56			filename);
57#else
58	if (disp_k) {
59		size++;
60		size >>= 1;
61	}
62	printf("%ld\t%s\n", size, filename);
63#endif
64}
65
66/* tiny recursive du */
67static long du(const char *const filename)
68{
69	struct stat statbuf;
70	long sum;
71
72	if (lstat(filename, &statbuf) != 0) {
73		bb_perror_msg("%s", filename);
74		status = EXIT_FAILURE;
75		return 0;
76	}
77
78	if (one_file_system) {
79		if (du_depth == 0) {
80			dir_dev = statbuf.st_dev;
81		} else if (dir_dev != statbuf.st_dev) {
82			return 0;
83		}
84	}
85
86	sum = statbuf.st_blocks;
87
88	if (S_ISLNK(statbuf.st_mode)) {
89		if (slink_depth > du_depth) {	/* -H or -L */
90			if (stat(filename, &statbuf) != 0) {
91				bb_perror_msg("%s", filename);
92				status = EXIT_FAILURE;
93				return 0;
94			}
95			sum = statbuf.st_blocks;
96			if (slink_depth == 1) {
97				slink_depth = INT_MAX;	/* Convert -H to -L. */
98			}
99		}
100	}
101
102	if (statbuf.st_nlink > count_hardlinks) {
103		/* Add files/directories with links only once */
104		if (is_in_ino_dev_hashtable(&statbuf)) {
105			return 0;
106		}
107		add_to_ino_dev_hashtable(&statbuf, NULL);
108	}
109
110	if (S_ISDIR(statbuf.st_mode)) {
111		DIR *dir;
112		struct dirent *entry;
113		char *newfile;
114
115		dir = warn_opendir(filename);
116		if (!dir) {
117			status = EXIT_FAILURE;
118			return sum;
119		}
120
121		newfile = last_char_is(filename, '/');
122		if (newfile)
123			*newfile = '\0';
124
125		while ((entry = readdir(dir))) {
126			char *name = entry->d_name;
127
128			newfile = concat_subpath_file(filename, name);
129			if (newfile == NULL)
130				continue;
131			++du_depth;
132			sum += du(newfile);
133			--du_depth;
134			free(newfile);
135		}
136		closedir(dir);
137	} else if (du_depth > print_files) {
138		return sum;
139	}
140	if (du_depth <= max_print_depth) {
141		print(sum, filename);
142	}
143	return sum;
144}
145
146int du_main(int argc, char **argv);
147int du_main(int argc, char **argv)
148{
149	long total;
150	int slink_depth_save;
151	int print_final_total;
152	char *smax_print_depth;
153	unsigned opt;
154
155#if ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
156	if (getenv("POSIXLY_CORRECT")) {	/* TODO - a new libbb function? */
157#if ENABLE_FEATURE_HUMAN_READABLE
158		disp_hr = 512;
159#else
160		disp_k = 0;
161#endif
162	}
163#endif
164
165	/* Note: SUSv3 specifies that -a and -s options cannot be used together
166	 * in strictly conforming applications.  However, it also says that some
167	 * du implementations may produce output when -a and -s are used together.
168	 * gnu du exits with an error code in this case.  We choose to simply
169	 * ignore -a.  This is consistent with -s being equivalent to -d 0.
170	 */
171#if ENABLE_FEATURE_HUMAN_READABLE
172	opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s";
173	opt = getopt32(argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth);
174	if (opt & (1 << 9)) {
175		/* -h opt */
176		disp_hr = 0;
177	}
178	if (opt & (1 << 10)) {
179		/* -m opt */
180		disp_hr = 1024*1024;
181	}
182	if (opt & (1 << 2)) {
183		/* -k opt */
184		disp_hr = 1024;
185	}
186#else
187	opt_complementary = "H-L:L-H:s-d:d-s";
188	opt = getopt32(argv, "aHkLsx" "d:" "lc", &smax_print_depth);
189#if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
190	if (opt & (1 << 2)) {
191		/* -k opt */
192		disp_k = 1;
193	}
194#endif
195#endif
196	if (opt & (1 << 0)) {
197		/* -a opt */
198		print_files = INT_MAX;
199	}
200	if (opt & (1 << 1)) {
201		/* -H opt */
202		slink_depth = 1;
203	}
204	if (opt & (1 << 3)) {
205		/* -L opt */
206		slink_depth = INT_MAX;
207	}
208	if (opt & (1 << 4)) {
209		/* -s opt */
210		max_print_depth = 0;
211	}
212	one_file_system = opt & (1 << 5); /* -x opt */
213	if (opt & (1 << 6)) {
214		/* -d opt */
215		max_print_depth = xatoi_u(smax_print_depth);
216	}
217	if (opt & (1 << 7)) {
218		/* -l opt */
219		count_hardlinks = MAXINT(nlink_t);
220	}
221	print_final_total = opt & (1 << 8); /* -c opt */
222
223	/* go through remaining args (if any) */
224	argv += optind;
225	if (optind >= argc) {
226		*--argv = (char*)".";
227		if (slink_depth == 1) {
228			slink_depth = 0;
229		}
230	}
231
232	slink_depth_save = slink_depth;
233	total = 0;
234	do {
235		total += du(*argv);
236		slink_depth = slink_depth_save;
237	} while (*++argv);
238	if (ENABLE_FEATURE_CLEAN_UP)
239		reset_ino_dev_hashtable();
240	if (print_final_total) {
241		print(total, "total");
242	}
243
244	fflush_stdout_and_exit(status);
245}
246