Bump to version 1.22.1
[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 /* FIXME? coreutils' du rounds sizes up:
93  * for example,  1025k file is shown as "2" by du -m.
94  * We round to nearest.
95  */
96 static void print(unsigned long long size, const char *filename)
97 {
98         /* TODO - May not want to defer error checking here. */
99 #if ENABLE_FEATURE_HUMAN_READABLE
100         printf("%s\t%s\n",
101                         /* size x 512 / G.disp_hr, show one fractional,
102                          * use suffixes if G.disp_hr == 0 */
103                         make_human_readable_str(size, 512, G.disp_hr),
104                         filename);
105 #else
106         if (G.disp_k) {
107                 size++;
108                 size >>= 1;
109         }
110         printf("%llu\t%s\n", size, filename);
111 #endif
112 }
113
114 /* tiny recursive du */
115 static unsigned long long du(const char *filename)
116 {
117         struct stat statbuf;
118         unsigned long long sum;
119
120         if (lstat(filename, &statbuf) != 0) {
121                 bb_simple_perror_msg(filename);
122                 G.status = EXIT_FAILURE;
123                 return 0;
124         }
125
126         if (option_mask32 & OPT_x_one_FS) {
127                 if (G.du_depth == 0) {
128                         G.dir_dev = statbuf.st_dev;
129                 } else if (G.dir_dev != statbuf.st_dev) {
130                         return 0;
131                 }
132         }
133
134         sum = statbuf.st_blocks;
135
136         if (S_ISLNK(statbuf.st_mode)) {
137                 if (G.slink_depth > G.du_depth) { /* -H or -L */
138                         if (stat(filename, &statbuf) != 0) {
139                                 bb_simple_perror_msg(filename);
140                                 G.status = EXIT_FAILURE;
141                                 return 0;
142                         }
143                         sum = statbuf.st_blocks;
144                         if (G.slink_depth == 1) {
145                                 /* Convert -H to -L */
146                                 G.slink_depth = INT_MAX;
147                         }
148                 }
149         }
150
151         if (!(option_mask32 & OPT_l_hardlinks)
152          && statbuf.st_nlink > 1
153         ) {
154                 /* Add files/directories with links only once */
155                 if (is_in_ino_dev_hashtable(&statbuf)) {
156                         return 0;
157                 }
158                 add_to_ino_dev_hashtable(&statbuf, NULL);
159         }
160
161         if (S_ISDIR(statbuf.st_mode)) {
162                 DIR *dir;
163                 struct dirent *entry;
164                 char *newfile;
165
166                 dir = warn_opendir(filename);
167                 if (!dir) {
168                         G.status = EXIT_FAILURE;
169                         return sum;
170                 }
171
172                 while ((entry = readdir(dir))) {
173                         newfile = concat_subpath_file(filename, entry->d_name);
174                         if (newfile == NULL)
175                                 continue;
176                         ++G.du_depth;
177                         sum += du(newfile);
178                         --G.du_depth;
179                         free(newfile);
180                 }
181                 closedir(dir);
182         } else {
183                 if (!(option_mask32 & OPT_a_files_too) && G.du_depth != 0)
184                         return sum;
185         }
186         if (G.du_depth <= G.max_print_depth) {
187                 print(sum, filename);
188         }
189         return sum;
190 }
191
192 int du_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
193 int du_main(int argc UNUSED_PARAM, char **argv)
194 {
195         unsigned long long total;
196         int slink_depth_save;
197         unsigned opt;
198
199         INIT_G();
200
201 #if ENABLE_FEATURE_HUMAN_READABLE
202         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 1024;)
203         IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_hr = 512;)
204         if (getenv("POSIXLY_CORRECT"))  /* TODO - a new libbb function? */
205                 G.disp_hr = 512;
206 #else
207         IF_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 1;)
208         /* IF_NOT_FEATURE_DU_DEFAULT_BLOCKSIZE_1K(G.disp_k = 0;) - G is pre-zeroed */
209 #endif
210         G.max_print_depth = INT_MAX;
211
212         /* Note: SUSv3 specifies that -a and -s options cannot be used together
213          * in strictly conforming applications.  However, it also says that some
214          * du implementations may produce output when -a and -s are used together.
215          * gnu du exits with an error code in this case.  We choose to simply
216          * ignore -a.  This is consistent with -s being equivalent to -d 0.
217          */
218 #if ENABLE_FEATURE_HUMAN_READABLE
219         opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s:d+";
220         opt = getopt32(argv, "aHkLsx" "d:" "lc" "hm", &G.max_print_depth);
221         argv += optind;
222         if (opt & OPT_h_for_humans) {
223                 G.disp_hr = 0;
224         }
225         if (opt & OPT_m_mbytes) {
226                 G.disp_hr = 1024*1024;
227         }
228         if (opt & OPT_k_kbytes) {
229                 G.disp_hr = 1024;
230         }
231 #else
232         opt_complementary = "H-L:L-H:s-d:d-s:d+";
233         opt = getopt32(argv, "aHkLsx" "d:" "lc", &G.max_print_depth);
234         argv += optind;
235 #if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
236         if (opt & OPT_k_kbytes) {
237                 G.disp_k = 1;
238         }
239 #endif
240 #endif
241         if (opt & OPT_H_follow_links) {
242                 G.slink_depth = 1;
243         }
244         if (opt & OPT_L_follow_links) {
245                 G.slink_depth = INT_MAX;
246         }
247         if (opt & OPT_s_total_norecurse) {
248                 G.max_print_depth = 0;
249         }
250
251         /* go through remaining args (if any) */
252         if (!*argv) {
253                 *--argv = (char*)".";
254                 if (G.slink_depth == 1) {
255                         G.slink_depth = 0;
256                 }
257         }
258
259         slink_depth_save = G.slink_depth;
260         total = 0;
261         do {
262                 total += du(*argv);
263                 /* otherwise du /dir /dir won't show /dir twice: */
264                 reset_ino_dev_hashtable();
265                 G.slink_depth = slink_depth_save;
266         } while (*++argv);
267
268         if (opt & OPT_c_total)
269                 print(total, "total");
270
271         fflush_stdout_and_exit(G.status);
272 }