fix subtle bug inherited from dash
[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 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 "busybox.h"
27
28 #if ENABLE_FEATURE_HUMAN_READABLE
29 # if ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
30 static unsigned long disp_hr = 1024;
31 # else
32 static unsigned long disp_hr = 512;
33 # endif
34 #elif ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
35 static unsigned disp_k = 1;
36 #else
37 static unsigned disp_k; /* bss inits to 0 */
38 #endif
39
40 static int max_print_depth = INT_MAX;
41 static nlink_t count_hardlinks = 1;
42
43 static int status;
44 static int print_files;
45 static int slink_depth;
46 static int du_depth;
47 static int one_file_system;
48 static dev_t dir_dev;
49
50
51 static 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 */
67 static 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, NULL)) {
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
146 int du_main(int argc, char **argv)
147 {
148         long total;
149         int slink_depth_save;
150         int print_final_total;
151         char *smax_print_depth;
152         unsigned opt;
153
154 #if ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
155         if (getenv("POSIXLY_CORRECT")) {        /* TODO - a new libbb function? */
156 #if ENABLE_FEATURE_HUMAN_READABLE
157                 disp_hr = 512;
158 #else
159                 disp_k = 0;
160 #endif
161         }
162 #endif
163
164         /* Note: SUSv3 specifies that -a and -s options cannot be used together
165          * in strictly conforming applications.  However, it also says that some
166          * du implementations may produce output when -a and -s are used together.
167          * gnu du exits with an error code in this case.  We choose to simply
168          * ignore -a.  This is consistent with -s being equivalent to -d 0.
169          */
170 #if ENABLE_FEATURE_HUMAN_READABLE
171         opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s";
172         opt = getopt32(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth);
173         if (opt & (1 << 9)) {
174                 /* -h opt */
175                 disp_hr = 0;
176         }
177         if (opt & (1 << 10)) {
178                 /* -m opt */
179                 disp_hr = 1024*1024;
180         }
181         if (opt & (1 << 2)) {
182                 /* -k opt */
183                 disp_hr = 1024;
184         }
185 #else
186         opt_complementary = "H-L:L-H:s-d:d-s";
187         opt = getopt32(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth);
188 #if !ENABLE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
189         if (opt & (1 << 2)) {
190                 /* -k opt */
191                 disp_k = 1;
192         }
193 #endif
194 #endif
195         if (opt & (1 << 0)) {
196                 /* -a opt */
197                 print_files = INT_MAX;
198         }
199         if (opt & (1 << 1)) {
200                 /* -H opt */
201                 slink_depth = 1;
202         }
203         if (opt & (1 << 3)) {
204                 /* -L opt */
205                 slink_depth = INT_MAX;
206         }
207         if (opt & (1 << 4)) {
208                 /* -s opt */
209                 max_print_depth = 0;
210         }
211         one_file_system = opt & (1 << 5); /* -x opt */
212         if (opt & (1 << 6)) {
213                 /* -d opt */
214                 max_print_depth = xatoi_u(smax_print_depth);
215         }
216         if (opt & (1 << 7)) {
217                 /* -l opt */
218                 count_hardlinks = MAXINT(nlink_t);
219         }
220         print_final_total = opt & (1 << 8); /* -c opt */
221
222         /* go through remaining args (if any) */
223         argv += optind;
224         if (optind >= argc) {
225                 *--argv = ".";
226                 if (slink_depth == 1) {
227                         slink_depth = 0;
228                 }
229         }
230
231         slink_depth_save = slink_depth;
232         total = 0;
233         do {
234                 total += du(*argv);
235                 slink_depth = slink_depth_save;
236         } while (*++argv);
237 #if ENABLE_FEATURE_CLEAN_UP
238         reset_ino_dev_hashtable();
239 #endif
240
241         if (print_final_total) {
242                 print(total, "total");
243         }
244
245         fflush_stdout_and_exit(status);
246 }