fd19855e1bc9b37882833b15d08853fc6f422ae6
[oweals/busybox.git] / coreutils / du.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini du implementation for busybox
4  *
5  *
6  * Copyright (C) 1999,2000,2001 by Lineo, inc.
7  * Written by John Beppu <beppu@lineo.com>
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 #include <sys/types.h>
26 #include <fcntl.h>
27 #include <dirent.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <getopt.h>
31 #include <string.h>
32 #include <errno.h>
33 #include "busybox.h"
34
35
36 #ifdef BB_FEATURE_HUMAN_READABLE
37 static unsigned long disp_hr = KILOBYTE;
38 #endif
39
40 typedef void (Display) (long, char *);
41
42 static int du_depth = 0;
43 static int count_hardlinks = 0;
44
45 static Display *print;
46
47 static void print_normal(long size, char *filename)
48 {
49         unsigned long base;
50 #ifdef BB_FEATURE_HUMAN_READABLE
51         switch (disp_hr) {
52                 case MEGABYTE:
53                         base = KILOBYTE;
54                         break;
55                 case KILOBYTE:
56                         base = 1;
57                         break;
58                 default:
59                         base = 0;
60         }
61         printf("%s\t%s\n", make_human_readable_str(size, base), filename);
62 #else
63         printf("%ld\t%s\n", size, filename);
64 #endif
65 }
66
67 static void print_summary(long size, char *filename)
68 {
69         if (du_depth == 1) {
70                 print_normal(size, filename);
71         }
72 }
73
74 #define HASH_SIZE       311             /* Should be prime */
75 #define hash_inode(i)   ((i) % HASH_SIZE)
76
77 typedef struct ino_dev_hash_bucket_struct {
78   struct ino_dev_hash_bucket_struct *next;
79   ino_t ino;
80   dev_t dev;
81   char name[1];
82 } ino_dev_hashtable_bucket_t;
83
84 static ino_dev_hashtable_bucket_t *ino_dev_hashtable[HASH_SIZE];
85
86 /*
87  * Return 1 if statbuf->st_ino && statbuf->st_dev are recorded in
88  * `ino_dev_hashtable', else return 0
89  *
90  * If NAME is a non-NULL pointer to a character pointer, and there is
91  * a match, then set *NAME to the value of the name slot in that
92  * bucket.
93  */
94 static int is_in_ino_dev_hashtable(const struct stat *statbuf, char **name)
95 {
96         ino_dev_hashtable_bucket_t *bucket;
97
98         bucket = ino_dev_hashtable[hash_inode(statbuf->st_ino)];
99         while (bucket != NULL) {
100           if ((bucket->ino == statbuf->st_ino) &&
101                   (bucket->dev == statbuf->st_dev))
102           {
103                 if (name) *name = bucket->name;
104                 return 1;
105           }
106           bucket = bucket->next;
107         }
108         return 0;
109 }
110
111 /* Add statbuf to statbuf hash table */
112 static void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name)
113 {
114         int i;
115         size_t s;
116         ino_dev_hashtable_bucket_t *bucket;
117
118         i = hash_inode(statbuf->st_ino);
119         s = name ? strlen(name) : 0;
120         bucket = xmalloc(sizeof(ino_dev_hashtable_bucket_t) + s);
121         bucket->ino = statbuf->st_ino;
122         bucket->dev = statbuf->st_dev;
123         if (name)
124                 strcpy(bucket->name, name);
125         else
126                 bucket->name[0] = '\0';
127         bucket->next = ino_dev_hashtable[i];
128         ino_dev_hashtable[i] = bucket;
129 }
130
131 /* Clear statbuf hash table */
132 static void reset_ino_dev_hashtable(void)
133 {
134         int i;
135         ino_dev_hashtable_bucket_t *bucket;
136
137         for (i = 0; i < HASH_SIZE; i++) {
138                 while (ino_dev_hashtable[i] != NULL) {
139                         bucket = ino_dev_hashtable[i]->next;
140                         free(ino_dev_hashtable[i]);
141                         ino_dev_hashtable[i] = bucket;
142                 }
143         }
144 }
145
146 /* tiny recursive du */
147 static long du(char *filename)
148 {
149         struct stat statbuf;
150         long sum;
151
152         if ((lstat(filename, &statbuf)) != 0) {
153                 perror_msg("%s", filename);
154                 return 0;
155         }
156
157         du_depth++;
158         sum = (statbuf.st_blocks >> 1);
159
160         /* Don't add in stuff pointed to by symbolic links */
161         if (S_ISLNK(statbuf.st_mode)) {
162                 sum = 0L;
163                 if (du_depth == 1)
164                         print(sum, filename);
165         }
166         if (S_ISDIR(statbuf.st_mode)) {
167                 DIR *dir;
168                 struct dirent *entry;
169                 char *newfile;
170
171                 dir = opendir(filename);
172                 if (!dir) {
173                         du_depth--;
174                         return 0;
175                 }
176
177                 newfile = last_char_is(filename, '/');
178                 if (newfile)
179                         *newfile = '\0';
180
181                 while ((entry = readdir(dir))) {
182                         char *name = entry->d_name;
183
184                         if ((strcmp(name, "..") == 0)
185                                 || (strcmp(name, ".") == 0)) {
186                                 continue;
187                         }
188                         newfile = concat_path_file(filename, name);
189                         sum += du(newfile);
190                         free(newfile);
191                 }
192                 closedir(dir);
193                 print(sum, filename);
194         }
195         else if (statbuf.st_nlink > 1 && !count_hardlinks) {
196                 /* Add files with hard links only once */
197                 if (is_in_ino_dev_hashtable(&statbuf, NULL)) {
198                         sum = 0L;
199                         if (du_depth == 1)
200                                 print(sum, filename);
201                 }
202                 else {
203                         add_to_ino_dev_hashtable(&statbuf, NULL);
204                 }
205         }
206         du_depth--;
207         return sum;
208 }
209
210 int du_main(int argc, char **argv)
211 {
212         int status = EXIT_SUCCESS;
213         int i;
214         int c;
215
216         /* default behaviour */
217         print = print_normal;
218
219         /* parse argv[] */
220         while ((c = getopt(argc, argv, "sl"
221 #ifdef BB_FEATURE_HUMAN_READABLE
222 "hm"
223 #endif
224 "k")) != EOF) {
225                         switch (c) {
226                         case 's':
227                                         print = print_summary;
228                                         break;
229                         case 'l':
230                                         count_hardlinks = 1;
231                                         break;
232 #ifdef BB_FEATURE_HUMAN_READABLE
233                         case 'h': disp_hr = 0;        break;
234                         case 'm': disp_hr = MEGABYTE; break;
235 #endif
236                         case 'k': break;
237                         default:
238                                         show_usage();
239                         }
240         }
241
242         /* go through remaining args (if any) */
243         if (optind >= argc) {
244                 if (du(".") == 0)
245                         status = EXIT_FAILURE;
246         } else {
247                 long sum;
248
249                 for (i=optind; i < argc; i++) {
250                         if ((sum = du(argv[i])) == 0)
251                                 status = EXIT_FAILURE;
252                         if(is_directory(argv[i], FALSE, NULL)==FALSE) {
253                                 print_normal(sum, argv[i]);
254                         }
255                         reset_ino_dev_hashtable();
256                 }
257         }
258
259         return status;
260 }
261
262 /* $Id: du.c,v 1.48 2001/06/01 21:47:15 andersen Exp $ */
263 /*
264 Local Variables:
265 c-file-style: "linux"
266 c-basic-offset: 4
267 tab-width: 4
268 End:
269 */