7984d657a227541ca3e034a8c3cbf24e4d1c6781
[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 = 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         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;
170         char *smax_print_depth;
171         unsigned long opt;
172
173 #ifdef CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
174         if (getenv("POSIXLY_CORRECT")) {        /* TODO - a new libbb function? */
175 #ifdef CONFIG_FEATURE_HUMAN_READABLE
176                 disp_hr = 512;
177 #else
178                 disp_k = 0;
179 #endif
180         } 
181 #endif
182
183         /* Note: SUSv3 specifies that -a and -s options can not be used together
184          * in strictly conforming applications.  However, it also says that some
185          * du implementations may produce output when -a and -s are used together.
186          * gnu du exits with an error code in this case.  We choose to simply
187          * ignore -a.  This is consistent with -s being equivalent to -d 0.
188          */
189 #ifdef CONFIG_FEATURE_HUMAN_READABLE
190         bb_opt_complementaly = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s";
191         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth);
192         if((opt & (1 << 9))) {
193                 /* -h opt */
194                 disp_hr = 0;
195         }
196         if((opt & (1 << 10))) {
197                 /* -m opt */
198                 disp_hr = MEGABYTE;
199         }
200         if((opt & (1 << 2))) {
201                 /* -k opt */
202                         disp_hr = KILOBYTE;
203         }
204 #else
205         bb_opt_complementaly = "H-L:L-H:s-d:d-s";
206         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth);
207 #if !defined CONFIG_FEATURE_DU_DEFALT_BLOCKSIZE_1K
208         if((opt & (1 << 2))) {
209                 /* -k opt */
210                         disp_k = 1;
211         }
212 #endif
213 #endif
214         if((opt & (1 << 0))) {
215                 /* -a opt */
216                 print_files = INT_MAX;
217         }
218         if((opt & (1 << 1))) {
219                 /* -H opt */
220                 slink_depth = 1;
221         }
222         if((opt & (1 << 3))) {
223                 /* -L opt */
224                         slink_depth = INT_MAX;
225         }
226         if((opt & (1 << 4))) {
227                 /* -s opt */
228                         max_print_depth = 0;
229                 }
230         one_file_system = opt & (1 << 5); /* -x opt */
231         if((opt & (1 << 6))) {
232                 /* -d opt */
233                 max_print_depth = bb_xgetularg10_bnd(smax_print_depth, 0, INT_MAX);
234         }
235         if((opt & (1 << 7))) {
236                 /* -l opt */
237                 count_hardlinks = INT_MAX;
238         }
239         print_final_total = opt & (1 << 8); /* -c opt */
240
241         /* go through remaining args (if any) */
242         argv += optind;
243         if (optind >= argc) {
244                 *--argv = ".";
245                 if (slink_depth == 1) {
246                         slink_depth = 0;
247                 }
248         }
249
250         slink_depth_save = slink_depth;
251         total = 0;
252         do {
253                 total += du(*argv);
254                 slink_depth = slink_depth_save;
255         } while (*++argv);
256 #ifdef CONFIG_FEATURE_CLEAN_UP
257         reset_ino_dev_hashtable();
258 #endif
259
260         if (print_final_total) {
261                 print(total, "total");
262         }
263
264         bb_fflush_stdout_and_exit(status);
265 }