Added the option parsing.
[dus.git] / dus.c
1
2 /*
3  *  dus is a simple utility to display the files and directories
4  *  according to their total disk occupancy.
5  *
6  *  Copyright (c) 2010 Francois Fleuret
7  *  Written by Francois Fleuret <francois@fleuret.org>
8  *
9  *  This file is part of dus.
10  *
11  *  dus is free software: you can redistribute it and/or modify it
12  *  under the terms of the GNU General Public License version 3 as
13  *  published by the Free Software Foundation.
14  *
15  *  dus is distributed in the hope that it will be useful, but WITHOUT
16  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17  *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
18  *  License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with dus.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24
25 #define _BSD_SOURCE
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <sys/ioctl.h>
36 #include <locale.h>
37 #include <getopt.h>
38
39 #define BUFFER_SIZE 4096
40
41 typedef int64_t size_sum_t;
42
43 /* Yeah, global variables! */
44
45 int ignore_dotfiles = 0; /* 1 means ignore files and directories
46                             starting with a dot */
47
48 int forced_height = 0; /* -1 means no height limit, strictly positive
49                            means limit, 0 means not active */
50
51 int fancy_size_display = 0; /* 1 means to use floating values with K, M and G
52                                as units */
53
54 /********************************************************************/
55
56 int ignore_entry(const char *name) {
57   return
58     strcmp(name, ".") == 0 ||
59     strcmp(name, "..") == 0 ||
60     (ignore_dotfiles && name[0] == '.');
61 }
62
63 void print_size_sum(size_sum_t s) {
64   char tmp[100];
65   char *a = tmp + sizeof(tmp)/sizeof(char);
66   *(--a) = '\0';
67   if(s) {
68     while(s) {
69       *(--a) = s%10 + '0';
70       s /= 10;
71     }
72   } else {
73     *(--a) = '0';
74   }
75   printf(a);
76 }
77
78 size_sum_t file_or_dir_size(const char *name) {
79   DIR *dir;
80   struct dirent *dir_e;
81   struct stat dummy;
82   size_sum_t result;
83   char subname[BUFFER_SIZE];
84
85   result = 0;
86
87   if(lstat(name, &dummy) != 0) {
88     printf("Can not stat %s: %s\n", name, strerror(errno));
89     exit(EXIT_FAILURE);
90   }
91
92   if(S_ISLNK(dummy.st_mode)) {
93     return 0;
94   }
95
96   dir = opendir(name);
97
98   if(dir) {
99     while((dir_e = readdir(dir))) {
100       if(!ignore_entry(dir_e->d_name)) {
101         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
102         result += file_or_dir_size(subname);
103       }
104     }
105     closedir(dir);
106   } else {
107     if(S_ISREG(dummy.st_mode)) {
108       result += dummy.st_size;
109     }
110   }
111
112   return result;
113 }
114
115 /**********************************************************************/
116
117 struct file_with_size {
118   char *filename;
119   size_sum_t size;
120   struct file_with_size *next;
121 };
122
123 struct file_with_size *create(char *name, struct file_with_size *current) {
124   struct file_with_size *result;
125   result = malloc(sizeof(struct file_with_size));
126   result->filename = strdup(name);
127   result->size = file_or_dir_size(name);
128   result->next = current;
129   return result;
130 }
131
132 void destroy(struct file_with_size *node) {
133   struct file_with_size *next;
134   while(node) {
135     next = node->next;
136     free(node->filename);
137     free(node);
138     node = next;
139   }
140 }
141
142 /**********************************************************************/
143
144 int compare_files(const void *x1, const void *x2) {
145   const struct file_with_size **f1, **f2;
146
147   f1 = (const struct file_with_size **) x1;
148   f2 = (const struct file_with_size **) x2;
149
150   if((*f1)->size < (*f2)->size) {
151     return -1;
152   } else if((*f1)->size > (*f2)->size) {
153     return 1;
154   } else {
155     return 0;
156   }
157 }
158
159 void print_sorted(struct file_with_size *root, int height) {
160   struct file_with_size *node;
161   struct file_with_size **nodes;
162   int nb, n, first;
163
164   nb = 0;
165   for(node = root; node; node = node->next) {
166     nb++;
167   }
168
169   nodes = malloc(nb * sizeof(struct file_with_size *));
170
171   n = 0;
172   for(node = root; node; node = node->next) {
173     nodes[n++] = node;
174   }
175
176   qsort(nodes, nb, sizeof(struct file_with_size *), compare_files);
177
178   first = 0;
179
180   if(forced_height) {
181     height = forced_height;
182   }
183
184   if(height > 0 && height < nb) {
185     first = nb - height;
186   }
187
188   if(fancy_size_display) {
189     for(n = first; n < nb; n++) {
190       if(nodes[n]->size < 1024) {
191         printf("% 7d %s\n",
192                ((int) nodes[n]->size),
193                nodes[n]->filename);
194       } else if(nodes[n]->size < 1024 * 1024) {
195         printf("% 6.1fK %s\n",
196                ((double) (nodes[n]->size))/(1024.0),
197                nodes[n]->filename);
198       } else if(nodes[n]->size < 1024 * 1024 * 1024) {
199         printf("% 6.1fM %s\n",
200                ((double) (nodes[n]->size))/(1024.0 * 1024),
201                nodes[n]->filename);
202       } else {
203         printf("% 6.1fG %s\n",
204                ((double) (nodes[n]->size))/(1024.0 * 1024.0 * 1024.0),
205                nodes[n]->filename);
206       }
207     }
208   } else {
209     for(n = first; n < nb; n++) {
210       print_size_sum(nodes[n]->size);
211       printf(" %s\n", nodes[n]->filename);
212     }
213   }
214
215   free(nodes);
216 }
217
218 /**********************************************************************/
219
220 int main(int argc, char **argv) {
221   char c;
222   struct file_with_size *root;
223
224   root = 0;
225
226   setlocale (LC_ALL, "");
227
228   while (1) {
229     c = getopt(argc, argv, "dfl:hdu");
230     if (c == -1)
231       break;
232
233     switch (c) {
234
235     case 'd':
236       ignore_dotfiles = 1;
237       break;
238
239     case 'f':
240       fancy_size_display = 1;
241       break;
242
243     case 'l':
244       forced_height = atoi(optarg);
245       break;
246
247     case 'h':
248       printf("Usage: dus [OPTION]... [FILE]...\n");
249       printf("List files and directories sorted according to their size or content \n");
250       printf("size.\n");
251       printf("\n");
252       printf("   -d         do not take into account files starting with a '.'\n");
253       printf("   -f         display size with float values and K, M and G units.\n");
254       printf("   -l <lines> specificy the number of lines to display. The value -1\n");
255       printf("              corresponds to all the lines. By default the command\n");
256       printf("              shows one line less than the number of lines of the TTY,\n");
257       printf("              or all the lines if standard output is not a tty\n");
258       printf("   -h         show this help.\n");
259       printf("\n");
260       printf("Report bugs and comments to <francois@fleuret.org>\n");
261       exit(EXIT_SUCCESS);
262
263       break;
264
265     default:
266       exit(EXIT_FAILURE);
267     }
268   }
269
270   if (optind < argc) {
271     while (optind < argc) {
272       root = create(argv[optind++], root);
273     }
274   } else {
275     DIR *dir;
276     struct dirent *dir_e;
277     dir = opendir(".");
278     if(dir) {
279       while((dir_e = readdir(dir))) {
280         if(!ignore_entry(dir_e->d_name)) {
281           root = create(dir_e->d_name, root);
282         }
283       }
284       closedir(dir);
285     }
286   }
287
288   if(isatty(STDOUT_FILENO)) {
289     struct winsize win;
290     if(ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &win)) {
291       printf("Can not get the tty size: %s\n", strerror(errno));
292       exit (EXIT_FAILURE);
293     }
294     print_sorted(root, win.ws_row - 2);
295   } else {
296     print_sorted(root, -1);
297   }
298
299   destroy(root);
300
301   exit(EXIT_SUCCESS);
302 }