du: trim help text a bit more
[platform/upstream/busybox.git] / coreutils / du.c
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 source tree.
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 //usage:#define du_trivial_usage
27 //usage:       "[-aHLdclsx" IF_FEATURE_HUMAN_READABLE("hm") "k] [FILE]..."
28 //usage:#define du_full_usage "\n\n"
29 //usage:       "Summarize disk space used for each FILE and/or directory\n"
30 //usage:     "\n        -a      Show file sizes too"
31 //usage:     "\n        -L      Follow all symlinks"
32 //usage:     "\n        -H      Follow symlinks on command line"
33 //usage:     "\n        -d N    Limit output to directories (and files with -a) of depth < N"
34 //usage:     "\n        -c      Show grand total"
35 //usage:     "\n        -l      Count sizes many times if hard linked"
36 //usage:     "\n        -s      Display only a total for each argument"
37 //usage:     "\n        -x      Skip directories on different filesystems"
38 //usage:        IF_FEATURE_HUMAN_READABLE(
39 //usage:     "\n        -h      Sizes in human readable format (e.g., 1K 243M 2G)"
40 //usage:     "\n        -m      Sizes in megabytes"
41 //usage:        )
42 //usage:     "\n        -k      Sizes in kilobytes" IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(" (default)")
43 //usage:        IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(
44 //usage:     "\n                Default unit is 512 bytes"
45 //usage:        )
46 //usage:
47 //usage:#define du_example_usage
48 //usage:       "$ du\n"
49 //usage:       "16      ./CVS\n"
50 //usage:       "12      ./kernel-patches/CVS\n"
51 //usage:       "80      ./kernel-patches\n"
52 //usage:       "12      ./tests/CVS\n"
53 //usage:       "36      ./tests\n"
54 //usage:       "12      ./scripts/CVS\n"
55 //usage:       "16      ./scripts\n"
56 //usage:       "12      ./docs/CVS\n"
57 //usage:       "104     ./docs\n"
58 //usage:       "2417    .\n"
59
60 #include "libbb.h"
61
62 enum {
63         OPT_a_files_too    = (1 << 0),
64         OPT_H_follow_links = (1 << 1),
65         OPT_k_kbytes       = (1 << 2),
66         OPT_L_follow_links = (1 << 3),
67         OPT_s_total_norecurse = (1 << 4),
68         OPT_x_one_FS       = (1 << 5),
69         OPT_d_maxdepth     = (1 << 6),
70         OPT_l_hardlinks    = (1 << 7),
71         OPT_c_total        = (1 << 8),
72         OPT_h_for_humans   = (1 << 9),
73         OPT_m_mbytes       = (1 << 10),
74 };
75
76 struct globals {
77 #if ENABLE_FEATURE_HUMAN_READABLE
78         unsigned long disp_hr;
79 #else
80         unsigned disp_k;
81 #endif
82         int max_print_depth;
83         bool status;
84         int slink_depth;
85         int du_depth;
86         dev_t dir_dev;
87 } FIX_ALIASING;
88 #define G (*(struct globals*)&bb_common_bufsiz1)
89 #define INIT_G() do { } while (0)
90
91
92 static void print(unsigned long long size, const char *filename)
93 {
94         /* TODO - May not want to defer error checking here. */
95 #if ENABLE_FEATURE_HUMAN_READABLE
96         printf("%s\t%s\n",
97                         /* size x 512 / G.disp_hr, show one fractional,
98                          * use suffixes if G.disp_hr == 0 */
99                         make_human_readable_str(size, 512, G.disp_hr),
100                         filename);
101 #else
102         if (G.disp_k) {
103                 size++;
104                 size >>= 1;
105         }
106         printf("%llu\t%s\n", size, filename);
107 #endif
108 }
109
110 /* tiny recursive du */
111 static unsigned long long du(const char *filename)
112 {
113         struct stat statbuf;
114         unsigned long long sum;
115
116         if (lstat(filename, &statbuf) != 0) {
117                 bb_simple_perror_msg(filename);
118                 G.status = EXIT_FAILURE;
119                 return 0;
120         }
121
122         if (option_mask32 & OPT_x_one_FS) {
123                 if (G.du_depth == 0) {
124                         G.dir_dev = statbuf.st_dev;
125                 } else if (G.dir_dev != statbuf.st_dev) {
126                         return 0;
127                 }
128         }
129
130         sum = statbuf.st_blocks;
131
132         if (S_ISLNK(statbuf.st_mode)) {
133                 if (G.slink_depth > G.du_depth) { /* -H or -L */
134                         if (stat(filename, &statbuf) != 0) {
135                                 bb_simple_perror_msg(filename);
136                                 G.status = EXIT_FAILURE;
137                                 return 0;
138                         }
139                         sum = statbuf.st_blocks;
140                         if (G.slink_depth == 1) {
141                                 /* Convert -H to -L */
142                                 G.slink_depth = INT_MAX;
143                         }
144                 }
145         }
146
147         if (!(option_mask32 & OPT_l_hardlinks)
148          && statbuf.st_nlink > 1
149         ) {
150                 /* Add files/directories with links only once */
151                 if (is_in_ino_dev_hashtable(&statbuf)) {
152                         return 0;
153                 }
154                 add_to_ino_dev_hashtable(&statbuf, NULL);
155         }
156
157         if (S_ISDIR(statbuf.st_mode)) {
158                 DIR *dir;
159                 struct dirent *entry;
160                 char *newfile;
161
162                 dir = warn_opendir(filename);
163                 if (!dir) {
164                         G.status = EXIT_FAILURE;
165                         return sum;
166                 }
167
168                 while ((entry = readdir(dir))) {
169                         newfile = concat_subpath_file(filename, entry->d_name);
170                         if (newfile == NULL)
171                                 continue;
172                         ++G.du_depth;
173                         sum += du(newfile);
174                         --G.du_depth;
175                         free(newfile);
176                 }
177                 closedir(dir);
178         } else {
179                 if (!(option_mask32 & OPT_a_files_too) && G.du_depth != 0)
180                         return sum;
181         }
182         if (G.du_depth <= G.max_print_depth) {
183                 print(sum, filename);
184         }
185         return sum;
186 }
187
188 int du_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
189 int du_main(int argc UNUSED_PARAM, char **argv)
190 {
191         unsigned long long total;
192         int slink_depth_save;
193         unsigned opt;
194
195         INIT_G();
196
197 #if ENABLE_FEATURE_HUMAN_READABLE
198         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 1024;)
199         IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 512;)
200         if (getenv("POSIXLY_CORRECT"))  /* TODO - a new libbb function? */
201                 G.disp_hr = 512;
202 #else
203         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 1;)
204         /* IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 0;) - G is pre-zeroed */
205 #endif
206         G.max_print_depth = INT_MAX;
207
208         /* Note: SUSv3 specifies that -a and -s options cannot be used together
209          * in strictly conforming applications.  However, it also says that some
210          * du implementations may produce output when -a and -s are used together.
211          * gnu du exits with an error code in this case.  We choose to simply
212          * ignore -a.  This is consistent with -s being equivalent to -d 0.
213          */
214 #if ENABLE_FEATURE_HUMAN_READABLE
215         opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s:d+";
216         opt = getopt32(argv, "aHkLsx" "d:" "lc" "hm", &G.max_print_depth);
217         argv += optind;
218         if (opt & OPT_h_for_humans) {
219                 G.disp_hr = 0;
220         }
221         if (opt & OPT_m_mbytes) {
222                 G.disp_hr = 1024*1024;
223         }
224         if (opt & OPT_k_kbytes) {
225                 G.disp_hr = 1024;
226         }
227 #else
228         opt_complementary = "H-L:L-H:s-d:d-s:d+";
229         opt = getopt32(argv, "aHkLsx" "d:" "lc", &G.max_print_depth);
230         argv += optind;
231 #if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
232         if (opt & OPT_k_kbytes) {
233                 G.disp_k = 1;
234         }
235 #endif
236 #endif
237         if (opt & OPT_H_follow_links) {
238                 G.slink_depth = 1;
239         }
240         if (opt & OPT_L_follow_links) {
241                 G.slink_depth = INT_MAX;
242         }
243         if (opt & OPT_s_total_norecurse) {
244                 G.max_print_depth = 0;
245         }
246
247         /* go through remaining args (if any) */
248         if (!*argv) {
249                 *--argv = (char*)".";
250                 if (G.slink_depth == 1) {
251                         G.slink_depth = 0;
252                 }
253         }
254
255         slink_depth_save = G.slink_depth;
256         total = 0;
257         do {
258                 total += du(*argv);
259                 /* otherwise du /dir /dir won't show /dir twice: */
260                 reset_ino_dev_hashtable();
261                 G.slink_depth = slink_depth_save;
262         } while (*++argv);
263
264         if (opt & OPT_c_total)
265                 print(total, "total");
266
267         fflush_stdout_and_exit(G.status);
268 }