Fix bug (wrong value computed) when reading file from stdin, implement
[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  * 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 = INT_MAX;
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         bb_printf("%ld\t%s\n", size >> disp_k, filename);
81 #endif
82 }
83
84 /* tiny recursive du */
85 static long du(char *filename)
86 {
87         struct stat statbuf;
88         long sum;
89
90         if ((lstat(filename, &statbuf)) != 0) {
91                 bb_perror_msg("%s", filename);
92                 status = EXIT_FAILURE;
93                 return 0;
94         }
95
96         if (one_file_system) {
97                 if (du_depth == 0) {
98                         dir_dev = statbuf.st_dev;
99                 } else if (dir_dev != statbuf.st_dev) {
100                         return 0;
101                 }
102         }
103
104         sum = statbuf.st_blocks;
105
106         if (S_ISLNK(statbuf.st_mode)) {
107                 if (slink_depth > du_depth) {   /* -H or -L */
108                         if ((stat(filename, &statbuf)) != 0) {
109                                 bb_perror_msg("%s", filename);
110                                 status = EXIT_FAILURE;
111                                 return 0;
112                         }
113                         sum = statbuf.st_blocks;
114                         if (slink_depth == 1) {
115                                 slink_depth = INT_MAX;  /* Convert -H to -L. */
116                         }
117                 }
118         }
119
120         if (statbuf.st_nlink > count_hardlinks) {
121                 /* Add files/directories with links only once */
122                 if (is_in_ino_dev_hashtable(&statbuf, NULL)) {
123                         return 0;
124                 }
125                 add_to_ino_dev_hashtable(&statbuf, NULL);
126         }
127
128         if (S_ISDIR(statbuf.st_mode)) {
129                 DIR *dir;
130                 struct dirent *entry;
131                 char *newfile;
132
133                 dir = opendir(filename);
134                 if (!dir) {
135                         bb_perror_msg("%s", filename);
136                         status = EXIT_FAILURE;
137                         return sum;
138                 }
139
140                 newfile = last_char_is(filename, '/');
141                 if (newfile)
142                         *newfile = '\0';
143
144                 while ((entry = readdir(dir))) {
145                         char *name = entry->d_name;
146
147                         newfile = concat_subpath_file(filename, name);
148                         if(newfile == NULL)
149                                 continue;
150                         ++du_depth;
151                         sum += du(newfile);
152                         --du_depth;
153                         free(newfile);
154                 }
155                 closedir(dir);
156         } else if (du_depth > print_files) {
157                 return sum;
158         }
159         if (du_depth <= max_print_depth) {
160                 print(sum, filename);
161         }
162         return sum;
163 }
164
165 int du_main(int argc, char **argv)
166 {
167         long total;
168         int slink_depth_save;
169         int print_final_total = 0;
170         int c;
171
172 #ifdef CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
173         if (getenv("POSIXLY_CORRECT")) {        /* TODO - a new libbb function? */
174 #ifdef CONFIG_FEATURE_HUMAN_READABLE
175                 disp_hr = 512;
176 #else
177                 disp_k = 0;
178 #endif
179         } 
180 #endif
181
182         /* Note: SUSv3 specifies that -a and -s options can not be used together
183          * in strictly conforming applications.  However, it also says that some
184          * du implementations may produce output when -a and -s are used together.
185          * gnu du exits with an error code in this case.  We choose to simply
186          * ignore -a.  This is consistent with -s being equivalent to -d 0.
187          */
188
189         while ((c = getopt(argc, argv, "aHkLsx" "d:" "lc"
190 #ifdef CONFIG_FEATURE_HUMAN_READABLE
191                                            "hm"
192 #endif
193                                            )) > 0) {
194                 switch (c) {
195                 case 'a':
196                         print_files = INT_MAX;
197                         break;
198                 case 'H':
199                         slink_depth = 1;
200                         break;
201                 case 'k':
202 #ifdef CONFIG_FEATURE_HUMAN_READABLE
203                         disp_hr = KILOBYTE;
204 #elif !defined CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
205                         disp_k = 1;
206 #endif
207                         break;
208                 case 'L':
209                         slink_depth = INT_MAX;
210                         break;
211                 case 's':
212                         max_print_depth = 0;
213                         break;
214                 case 'x':
215                         one_file_system = 1;
216                         break;
217
218                 case 'd':
219                         max_print_depth = bb_xgetularg10_bnd(optarg, 0, INT_MAX);
220                         break;
221                 case 'l':
222                         count_hardlinks = 1;
223                         break;
224                 case 'c':
225                         print_final_total = 1;
226                         break;
227 #ifdef CONFIG_FEATURE_HUMAN_READABLE
228                 case 'h':
229                         disp_hr = 0;
230                         break;
231                 case 'm':
232                         disp_hr = MEGABYTE;
233                         break;
234 #endif
235                 default:
236                         bb_show_usage();
237                 }
238         }
239
240         /* go through remaining args (if any) */
241         argv += optind;
242         if (optind >= argc) {
243                 *--argv = ".";
244                 if (slink_depth == 1) {
245                         slink_depth = 0;
246                 }
247         }
248
249         slink_depth_save = slink_depth;
250         total = 0;
251         do {
252                 total += du(*argv);
253                 slink_depth = slink_depth_save;
254         } while (*++argv);
255         reset_ino_dev_hashtable();
256
257         if (print_final_total) {
258                 print(total, "total");
259         }
260
261         bb_fflush_stdout_and_exit(status);
262 }