Start 1.33.0 development cycle
[oweals/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 /* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
12  *
13  * Mostly rewritten for SUSv3 compliance and to fix bugs/defects.
14  * 1) Added support for SUSv3 -a, -H, -L, gnu -c, and (busybox) -d options.
15  *    The -d option allows setting of max depth (similar to gnu --max-depth).
16  * 2) Fixed incorrect size calculations for links and directories, especially
17  *    when errors occurred.  Calculates sizes should now match gnu du output.
18  * 3) Added error checking of output.
19  * 4) Fixed busybox bug #1284 involving long overflow with human_readable.
20  */
21 //config:config DU
22 //config:       bool "du (6.3 kb)"
23 //config:       default y
24 //config:       help
25 //config:       du is used to report the amount of disk space used
26 //config:       for specified files.
27 //config:
28 //config:config FEATURE_DU_DEFAULT_BLOCKSIZE_1K
29 //config:       bool "Use default blocksize of 1024 bytes (else it's 512 bytes)"
30 //config:       default y
31 //config:       depends on DU
32
33 //applet:IF_DU(APPLET(du, BB_DIR_USR_BIN, BB_SUID_DROP))
34
35 //kbuild:lib-$(CONFIG_DU) += du.o
36
37 /* BB_AUDIT SUSv3 compliant (unless default blocksize set to 1k) */
38 /* http://www.opengroup.org/onlinepubs/007904975/utilities/du.html */
39
40 //usage:#define du_trivial_usage
41 //usage:       "[-aHLdclsx" IF_FEATURE_HUMAN_READABLE("hm") "k] [FILE]..."
42 //usage:#define du_full_usage "\n\n"
43 //usage:       "Summarize disk space used for each FILE and/or directory\n"
44 //usage:     "\n        -a      Show file sizes too"
45 //usage:     "\n        -L      Follow all symlinks"
46 //usage:     "\n        -H      Follow symlinks on command line"
47 //usage:     "\n        -d N    Limit output to directories (and files with -a) of depth < N"
48 //usage:     "\n        -c      Show grand total"
49 //usage:     "\n        -l      Count sizes many times if hard linked"
50 //usage:     "\n        -s      Display only a total for each argument"
51 //usage:     "\n        -x      Skip directories on different filesystems"
52 //usage:        IF_FEATURE_HUMAN_READABLE(
53 //usage:     "\n        -h      Sizes in human readable format (e.g., 1K 243M 2G)"
54 //usage:     "\n        -m      Sizes in megabytes"
55 //usage:        )
56 //usage:     "\n        -k      Sizes in kilobytes" IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(" (default)")
57 //usage:        IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(
58 //usage:     "\n                Default unit is 512 bytes"
59 //usage:        )
60 //usage:
61 //usage:#define du_example_usage
62 //usage:       "$ du\n"
63 //usage:       "16      ./CVS\n"
64 //usage:       "12      ./kernel-patches/CVS\n"
65 //usage:       "80      ./kernel-patches\n"
66 //usage:       "12      ./tests/CVS\n"
67 //usage:       "36      ./tests\n"
68 //usage:       "12      ./scripts/CVS\n"
69 //usage:       "16      ./scripts\n"
70 //usage:       "12      ./docs/CVS\n"
71 //usage:       "104     ./docs\n"
72 //usage:       "2417    .\n"
73
74 #include "libbb.h"
75 #include "common_bufsiz.h"
76
77 enum {
78         OPT_a_files_too    = (1 << 0),
79         OPT_H_follow_links = (1 << 1),
80         OPT_k_kbytes       = (1 << 2),
81         OPT_L_follow_links = (1 << 3),
82         OPT_s_total_norecurse = (1 << 4),
83         OPT_x_one_FS       = (1 << 5),
84         OPT_d_maxdepth     = (1 << 6),
85         OPT_l_hardlinks    = (1 << 7),
86         OPT_c_total        = (1 << 8),
87         OPT_h_for_humans   = (1 << 9),
88         OPT_m_mbytes       = (1 << 10),
89 };
90
91 struct globals {
92 #if ENABLE_FEATURE_HUMAN_READABLE
93         unsigned long disp_unit;
94 #else
95         unsigned disp_k;
96 #endif
97         int max_print_depth;
98         bool status;
99         int slink_depth;
100         int du_depth;
101         dev_t dir_dev;
102 } FIX_ALIASING;
103 #define G (*(struct globals*)bb_common_bufsiz1)
104 #define INIT_G() do { setup_common_bufsiz(); } while (0)
105
106
107 static void print(unsigned long long size, const char *filename)
108 {
109         /* TODO - May not want to defer error checking here. */
110 #if ENABLE_FEATURE_HUMAN_READABLE
111 # if ENABLE_DESKTOP
112         /* ~30 bytes of code for extra comtat:
113          * coreutils' du rounds sizes up:
114          * for example,  1025k file is shown as "2" by du -m.
115          * We round to nearest if human-readable [too hard to fix],
116          * else (fixed scale such as -m), we round up. To that end,
117          * add yet another half of the unit before displaying:
118          */
119         if (G.disp_unit)
120                 size += (G.disp_unit-1) / (unsigned)(512 * 2);
121 # endif
122         printf("%s\t%s\n",
123                         /* size x 512 / G.disp_unit.
124                          * If G.disp_unit == 0, show one fractional
125                          * and use suffixes
126                          */
127                         make_human_readable_str(size, 512, G.disp_unit),
128                         filename);
129 #else
130         if (G.disp_k) {
131                 size++;
132                 size >>= 1;
133         }
134         printf("%llu\t%s\n", size, filename);
135 #endif
136 }
137
138 /* tiny recursive du */
139 static unsigned long long du(const char *filename)
140 {
141         struct stat statbuf;
142         unsigned long long sum;
143
144         if (lstat(filename, &statbuf) != 0) {
145                 bb_simple_perror_msg(filename);
146                 G.status = EXIT_FAILURE;
147                 return 0;
148         }
149
150         if (option_mask32 & OPT_x_one_FS) {
151                 if (G.du_depth == 0) {
152                         G.dir_dev = statbuf.st_dev;
153                 } else if (G.dir_dev != statbuf.st_dev) {
154                         return 0;
155                 }
156         }
157
158         sum = statbuf.st_blocks;
159
160         if (S_ISLNK(statbuf.st_mode)) {
161                 if (G.slink_depth > G.du_depth) { /* -H or -L */
162                         if (stat(filename, &statbuf) != 0) {
163                                 bb_simple_perror_msg(filename);
164                                 G.status = EXIT_FAILURE;
165                                 return 0;
166                         }
167                         sum = statbuf.st_blocks;
168                         if (G.slink_depth == 1) {
169                                 /* Convert -H to -L */
170                                 G.slink_depth = INT_MAX;
171                         }
172                 }
173         }
174
175         if (!(option_mask32 & OPT_l_hardlinks)
176          && statbuf.st_nlink > 1
177         ) {
178                 /* Add files/directories with links only once */
179                 if (is_in_ino_dev_hashtable(&statbuf)) {
180                         return 0;
181                 }
182                 add_to_ino_dev_hashtable(&statbuf, NULL);
183         }
184
185         if (S_ISDIR(statbuf.st_mode)) {
186                 DIR *dir;
187                 struct dirent *entry;
188                 char *newfile;
189
190                 dir = warn_opendir(filename);
191                 if (!dir) {
192                         G.status = EXIT_FAILURE;
193                         return sum;
194                 }
195
196                 while ((entry = readdir(dir))) {
197                         newfile = concat_subpath_file(filename, entry->d_name);
198                         if (newfile == NULL)
199                                 continue;
200                         ++G.du_depth;
201                         sum += du(newfile);
202                         --G.du_depth;
203                         free(newfile);
204                 }
205                 closedir(dir);
206         } else {
207                 if (!(option_mask32 & OPT_a_files_too) && G.du_depth != 0)
208                         return sum;
209         }
210         if (G.du_depth <= G.max_print_depth) {
211                 print(sum, filename);
212         }
213         return sum;
214 }
215
216 int du_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
217 int du_main(int argc UNUSED_PARAM, char **argv)
218 {
219         unsigned long long total;
220         int slink_depth_save;
221         unsigned opt;
222
223         INIT_G();
224
225 #if ENABLE_FEATURE_HUMAN_READABLE
226         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_unit = 1024;)
227         IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_unit = 512;)
228         if (getenv("POSIXLY_CORRECT"))  /* TODO - a new libbb function? */
229                 G.disp_unit = 512;
230 #else
231         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 1;)
232         /* IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 0;) - G is pre-zeroed */
233 #endif
234         G.max_print_depth = INT_MAX;
235
236         /* Note: SUSv3 specifies that -a and -s options cannot be used together
237          * in strictly conforming applications.  However, it also says that some
238          * du implementations may produce output when -a and -s are used together.
239          * gnu du exits with an error code in this case.  We choose to simply
240          * ignore -a.  This is consistent with -s being equivalent to -d 0.
241          */
242 #if ENABLE_FEATURE_HUMAN_READABLE
243         opt = getopt32(argv, "^"
244                         "aHkLsxd:+lchm"
245                         "\0" "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s",
246                         &G.max_print_depth
247         );
248         argv += optind;
249         if (opt & OPT_h_for_humans) {
250                 G.disp_unit = 0;
251         }
252         if (opt & OPT_m_mbytes) {
253                 G.disp_unit = 1024*1024;
254         }
255         if (opt & OPT_k_kbytes) {
256                 G.disp_unit = 1024;
257         }
258 #else
259         opt = getopt32(argv, "^"
260                         "aHkLsxd:+lc"
261                         "\0" "H-L:L-H:s-d:d-s",
262                         &G.max_print_depth
263         );
264         argv += optind;
265 #if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
266         if (opt & OPT_k_kbytes) {
267                 G.disp_k = 1;
268         }
269 #endif
270 #endif
271         if (opt & OPT_H_follow_links) {
272                 G.slink_depth = 1;
273         }
274         if (opt & OPT_L_follow_links) {
275                 G.slink_depth = INT_MAX;
276         }
277         if (opt & OPT_s_total_norecurse) {
278                 G.max_print_depth = 0;
279         }
280
281         /* go through remaining args (if any) */
282         if (!*argv) {
283                 *--argv = (char*)".";
284                 if (G.slink_depth == 1) {
285                         G.slink_depth = 0;
286                 }
287         }
288
289         slink_depth_save = G.slink_depth;
290         total = 0;
291         do {
292                 total += du(*argv);
293                 G.slink_depth = slink_depth_save;
294         } while (*++argv);
295
296         if (ENABLE_FEATURE_CLEAN_UP)
297                 reset_ino_dev_hashtable();
298         if (opt & OPT_c_total)
299                 print(total, "total");
300
301         fflush_stdout_and_exit(G.status);
302 }