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