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