When displaying the size in 1kB blocks round up if an odd number of
[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  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  *
23  */
24
25 /* BB_AUDIT SUSv3 compliant (unless default blocksize set to 1k) */
26 /* http://www.opengroup.org/onlinepubs/007904975/utilities/du.html */
27
28 /* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
29  *
30  * Mostly rewritten for SUSv3 compliance and to fix bugs/defects.
31  * 1) Added support for SUSv3 -a, -H, -L, gnu -c, and (busybox) -d options.
32  *    The -d option allows setting of max depth (similar to gnu --max-depth).
33  * 2) Fixed incorrect size calculations for links and directories, especially
34  *    when errors occurred.  Calculates sizes should now match gnu du output.
35  * 3) Added error checking of output.
36  * 4) Fixed busybox bug #1284 involving long overflow with human_readable.
37  */
38
39 #include <stdlib.h>
40 #include <limits.h>
41 #include <unistd.h>
42 #include <dirent.h>
43 #include <sys/stat.h>
44 #include "busybox.h"
45
46 #ifdef CONFIG_FEATURE_HUMAN_READABLE
47 # ifdef CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
48 static unsigned long disp_hr = KILOBYTE;
49 # else
50 static unsigned long disp_hr = 512;
51 # endif
52 #elif defined CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
53 static unsigned int disp_k = 1;
54 #else
55 static unsigned int disp_k;     /* bss inits to 0 */
56 #endif
57
58 static int max_print_depth = INT_MAX;
59 static int count_hardlinks = 1;
60
61 static int status
62 #if EXIT_SUCCESS == 0
63         = EXIT_SUCCESS
64 #endif
65         ;
66 static int print_files;
67 static int slink_depth;
68 static int du_depth;
69 static int one_file_system;
70 static dev_t dir_dev;
71
72
73 static void print(long size, char *filename)
74 {
75         /* TODO - May not want to defer error checking here. */
76 #ifdef CONFIG_FEATURE_HUMAN_READABLE
77         bb_printf("%s\t%s\n", make_human_readable_str(size, 512, disp_hr),
78                    filename);
79 #else
80         if (disp_k) {
81                 size++;
82                 size >>= 1;
83         }
84         bb_printf("%ld\t%s\n", size, filename);
85 #endif
86 }
87
88 /* tiny recursive du */
89 static long du(char *filename)
90 {
91         struct stat statbuf;
92         long sum;
93
94         if ((lstat(filename, &statbuf)) != 0) {
95                 bb_perror_msg("%s", filename);
96                 status = EXIT_FAILURE;
97                 return 0;
98         }
99
100         if (one_file_system) {
101                 if (du_depth == 0) {
102                         dir_dev = statbuf.st_dev;
103                 } else if (dir_dev != statbuf.st_dev) {
104                         return 0;
105                 }
106         }
107
108         sum = statbuf.st_blocks;
109
110         if (S_ISLNK(statbuf.st_mode)) {
111                 if (slink_depth > du_depth) {   /* -H or -L */
112                         if ((stat(filename, &statbuf)) != 0) {
113                                 bb_perror_msg("%s", filename);
114                                 status = EXIT_FAILURE;
115                                 return 0;
116                         }
117                         sum = statbuf.st_blocks;
118                         if (slink_depth == 1) {
119                                 slink_depth = INT_MAX;  /* Convert -H to -L. */
120                         }
121                 }
122         }
123
124         if (statbuf.st_nlink > count_hardlinks) {
125                 /* Add files/directories with links only once */
126                 if (is_in_ino_dev_hashtable(&statbuf, NULL)) {
127                         return 0;
128                 }
129                 add_to_ino_dev_hashtable(&statbuf, NULL);
130         }
131
132         if (S_ISDIR(statbuf.st_mode)) {
133                 DIR *dir;
134                 struct dirent *entry;
135                 char *newfile;
136
137                 dir = opendir(filename);
138                 if (!dir) {
139                         bb_perror_msg("%s", filename);
140                         status = EXIT_FAILURE;
141                         return sum;
142                 }
143
144                 newfile = last_char_is(filename, '/');
145                 if (newfile)
146                         *newfile = '\0';
147
148                 while ((entry = readdir(dir))) {
149                         char *name = entry->d_name;
150
151                         newfile = concat_subpath_file(filename, name);
152                         if(newfile == NULL)
153                                 continue;
154                         ++du_depth;
155                         sum += du(newfile);
156                         --du_depth;
157                         free(newfile);
158                 }
159                 closedir(dir);
160         } else if (du_depth > print_files) {
161                 return sum;
162         }
163         if (du_depth <= max_print_depth) {
164                 print(sum, filename);
165         }
166         return sum;
167 }
168
169 int du_main(int argc, char **argv)
170 {
171         long total;
172         int slink_depth_save;
173         int print_final_total;
174         char *smax_print_depth;
175         unsigned long opt;
176
177 #ifdef CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
178         if (getenv("POSIXLY_CORRECT")) {        /* TODO - a new libbb function? */
179 #ifdef CONFIG_FEATURE_HUMAN_READABLE
180                 disp_hr = 512;
181 #else
182                 disp_k = 0;
183 #endif
184         } 
185 #endif
186
187         /* Note: SUSv3 specifies that -a and -s options can not be used together
188          * in strictly conforming applications.  However, it also says that some
189          * du implementations may produce output when -a and -s are used together.
190          * gnu du exits with an error code in this case.  We choose to simply
191          * ignore -a.  This is consistent with -s being equivalent to -d 0.
192          */
193 #ifdef CONFIG_FEATURE_HUMAN_READABLE
194         bb_opt_complementaly = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s";
195         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth);
196         if((opt & (1 << 9))) {
197                 /* -h opt */
198                 disp_hr = 0;
199         }
200         if((opt & (1 << 10))) {
201                 /* -m opt */
202                 disp_hr = MEGABYTE;
203         }
204         if((opt & (1 << 2))) {
205                 /* -k opt */
206                         disp_hr = KILOBYTE;
207         }
208 #else
209         bb_opt_complementaly = "H-L:L-H:s-d:d-s";
210         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth);
211 #if !defined CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
212         if((opt & (1 << 2))) {
213                 /* -k opt */
214                         disp_k = 1;
215         }
216 #endif
217 #endif
218         if((opt & (1 << 0))) {
219                 /* -a opt */
220                 print_files = INT_MAX;
221         }
222         if((opt & (1 << 1))) {
223                 /* -H opt */
224                 slink_depth = 1;
225         }
226         if((opt & (1 << 3))) {
227                 /* -L opt */
228                         slink_depth = INT_MAX;
229         }
230         if((opt & (1 << 4))) {
231                 /* -s opt */
232                         max_print_depth = 0;
233                 }
234         one_file_system = opt & (1 << 5); /* -x opt */
235         if((opt & (1 << 6))) {
236                 /* -d opt */
237                 max_print_depth = bb_xgetularg10_bnd(smax_print_depth, 0, INT_MAX);
238         }
239         if((opt & (1 << 7))) {
240                 /* -l opt */
241                 count_hardlinks = INT_MAX;
242         }
243         print_final_total = opt & (1 << 8); /* -c opt */
244
245         /* go through remaining args (if any) */
246         argv += optind;
247         if (optind >= argc) {
248                 *--argv = ".";
249                 if (slink_depth == 1) {
250                         slink_depth = 0;
251                 }
252         }
253
254         slink_depth_save = slink_depth;
255         total = 0;
256         do {
257                 total += du(*argv);
258                 slink_depth = slink_depth_save;
259         } while (*++argv);
260 #ifdef CONFIG_FEATURE_CLEAN_UP
261         reset_ino_dev_hashtable();
262 #endif
263
264         if (print_final_total) {
265                 print(total, "total");
266         }
267
268         bb_fflush_stdout_and_exit(status);
269 }