Another whack at scripts/individual. Now builds 212 applets.
[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 <stdlib.h>
27 #include <limits.h>
28 #include <unistd.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 #include "busybox.h"
32
33 #ifdef CONFIG_FEATURE_HUMAN_READABLE
34 # ifdef CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
35 static unsigned long disp_hr = KILOBYTE;
36 # else
37 static unsigned long disp_hr = 512;
38 # endif
39 #elif defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
40 static unsigned int disp_k = 1;
41 #else
42 static unsigned int disp_k;     /* bss inits to 0 */
43 #endif
44
45 static int max_print_depth = INT_MAX;
46 static nlink_t count_hardlinks = 1;
47
48 static int status
49 #if EXIT_SUCCESS == 0
50         = EXIT_SUCCESS
51 #endif
52         ;
53 static int print_files;
54 static int slink_depth;
55 static int du_depth;
56 static int one_file_system;
57 static dev_t dir_dev;
58
59
60 static void print(long size, char *filename)
61 {
62         /* TODO - May not want to defer error checking here. */
63 #ifdef CONFIG_FEATURE_HUMAN_READABLE
64         bb_printf("%s\t%s\n", make_human_readable_str(size, 512, disp_hr),
65                    filename);
66 #else
67         if (disp_k) {
68                 size++;
69                 size >>= 1;
70         }
71         bb_printf("%ld\t%s\n", size, filename);
72 #endif
73 }
74
75 /* tiny recursive du */
76 static long du(char *filename)
77 {
78         struct stat statbuf;
79         long sum;
80
81         if ((lstat(filename, &statbuf)) != 0) {
82                 bb_perror_msg("%s", filename);
83                 status = EXIT_FAILURE;
84                 return 0;
85         }
86
87         if (one_file_system) {
88                 if (du_depth == 0) {
89                         dir_dev = statbuf.st_dev;
90                 } else if (dir_dev != statbuf.st_dev) {
91                         return 0;
92                 }
93         }
94
95         sum = statbuf.st_blocks;
96
97         if (S_ISLNK(statbuf.st_mode)) {
98                 if (slink_depth > du_depth) {   /* -H or -L */
99                         if ((stat(filename, &statbuf)) != 0) {
100                                 bb_perror_msg("%s", filename);
101                                 status = EXIT_FAILURE;
102                                 return 0;
103                         }
104                         sum = statbuf.st_blocks;
105                         if (slink_depth == 1) {
106                                 slink_depth = INT_MAX;  /* Convert -H to -L. */
107                         }
108                 }
109         }
110
111         if (statbuf.st_nlink > count_hardlinks) {
112                 /* Add files/directories with links only once */
113                 if (is_in_ino_dev_hashtable(&statbuf, NULL)) {
114                         return 0;
115                 }
116                 add_to_ino_dev_hashtable(&statbuf, NULL);
117         }
118
119         if (S_ISDIR(statbuf.st_mode)) {
120                 DIR *dir;
121                 struct dirent *entry;
122                 char *newfile;
123
124                 dir = warn_opendir(filename);
125                 if (!dir) {
126                         status = EXIT_FAILURE;
127                         return sum;
128                 }
129
130                 newfile = last_char_is(filename, '/');
131                 if (newfile)
132                         *newfile = '\0';
133
134                 while ((entry = readdir(dir))) {
135                         char *name = entry->d_name;
136
137                         newfile = concat_subpath_file(filename, name);
138                         if(newfile == NULL)
139                                 continue;
140                         ++du_depth;
141                         sum += du(newfile);
142                         --du_depth;
143                         free(newfile);
144                 }
145                 closedir(dir);
146         } else if (du_depth > print_files) {
147                 return sum;
148         }
149         if (du_depth <= max_print_depth) {
150                 print(sum, filename);
151         }
152         return sum;
153 }
154
155 int du_main(int argc, char **argv)
156 {
157         long total;
158         int slink_depth_save;
159         int print_final_total;
160         char *smax_print_depth;
161         unsigned long opt;
162
163 #ifdef CONFIG_FEATURE_DU_DEFUALT_BLOCKSIZE_1K
164         if (getenv("POSIXLY_CORRECT")) {        /* TODO - a new libbb function? */
165 #ifdef CONFIG_FEATURE_HUMAN_READABLE
166                 disp_hr = 512;
167 #else
168                 disp_k = 0;
169 #endif
170         }
171 #endif
172
173         /* Note: SUSv3 specifies that -a and -s options can not be used together
174          * in strictly conforming applications.  However, it also says that some
175          * du implementations may produce output when -a and -s are used together.
176          * gnu du exits with an error code in this case.  We choose to simply
177          * ignore -a.  This is consistent with -s being equivalent to -d 0.
178          */
179 #ifdef CONFIG_FEATURE_HUMAN_READABLE
180         bb_opt_complementally = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s";
181         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth);
182         if((opt & (1 << 9))) {
183                 /* -h opt */
184                 disp_hr = 0;
185         }
186         if((opt & (1 << 10))) {
187                 /* -m opt */
188                 disp_hr = MEGABYTE;
189         }
190         if((opt & (1 << 2))) {
191                 /* -k opt */
192                         disp_hr = KILOBYTE;
193         }
194 #else
195         bb_opt_complementally = "H-L:L-H:s-d:d-s";
196         opt = bb_getopt_ulflags(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth);
197 #if !defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K
198         if((opt & (1 << 2))) {
199                 /* -k opt */
200                         disp_k = 1;
201         }
202 #endif
203 #endif
204         if((opt & (1 << 0))) {
205                 /* -a opt */
206                 print_files = INT_MAX;
207         }
208         if((opt & (1 << 1))) {
209                 /* -H opt */
210                 slink_depth = 1;
211         }
212         if((opt & (1 << 3))) {
213                 /* -L opt */
214                         slink_depth = INT_MAX;
215         }
216         if((opt & (1 << 4))) {
217                 /* -s opt */
218                         max_print_depth = 0;
219                 }
220         one_file_system = opt & (1 << 5); /* -x opt */
221         if((opt & (1 << 6))) {
222                 /* -d opt */
223                 max_print_depth = bb_xgetularg10_bnd(smax_print_depth, 0, INT_MAX);
224         }
225         if((opt & (1 << 7))) {
226                 /* -l opt */
227                 count_hardlinks = INT_MAX;
228         }
229         print_final_total = opt & (1 << 8); /* -c opt */
230
231         /* go through remaining args (if any) */
232         argv += optind;
233         if (optind >= argc) {
234                 *--argv = ".";
235                 if (slink_depth == 1) {
236                         slink_depth = 0;
237                 }
238         }
239
240         slink_depth_save = slink_depth;
241         total = 0;
242         do {
243                 total += du(*argv);
244                 slink_depth = slink_depth_save;
245         } while (*++argv);
246 #ifdef CONFIG_FEATURE_CLEAN_UP
247         reset_ino_dev_hashtable();
248 #endif
249
250         if (print_final_total) {
251                 print(total, "total");
252         }
253
254         bb_fflush_stdout_and_exit(status);
255 }