Added an option to show the top of the list instead of the bottom.
[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 int reverse_sorting = 0; /* 1 means to show the large ones first */
55
56 int show_top = 0; /* 1 means to show the top of the sorted list
57                      instead of the bottom */
58
59 /********************************************************************/
60
61 int ignore_entry(const char *name) {
62   return
63     strcmp(name, ".") == 0 ||
64     strcmp(name, "..") == 0 ||
65     (ignore_dotfiles && name[0] == '.');
66 }
67
68 void print_size_sum(size_sum_t s) {
69   char tmp[100];
70   char *a = tmp + sizeof(tmp)/sizeof(char);
71   *(--a) = '\0';
72   if(s) {
73     while(s) {
74       *(--a) = s%10 + '0';
75       s /= 10;
76     }
77   } else {
78     *(--a) = '0';
79   }
80   printf(a);
81 }
82
83 size_sum_t file_or_dir_size(const char *name) {
84   DIR *dir;
85   struct dirent *dir_e;
86   struct stat dummy;
87   size_sum_t result;
88   char subname[BUFFER_SIZE];
89
90   result = 0;
91
92   if(lstat(name, &dummy) != 0) {
93     printf("Can not stat %s: %s\n", name, strerror(errno));
94     exit(EXIT_FAILURE);
95   }
96
97   if(S_ISLNK(dummy.st_mode)) {
98     return 0;
99   }
100
101   dir = opendir(name);
102
103   if(dir) {
104     while((dir_e = readdir(dir))) {
105       if(!ignore_entry(dir_e->d_name)) {
106         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
107         result += file_or_dir_size(subname);
108       }
109     }
110     closedir(dir);
111   } else {
112     if(S_ISREG(dummy.st_mode)) {
113       result += dummy.st_size;
114     }
115   }
116
117   return result;
118 }
119
120 /**********************************************************************/
121
122 struct file_with_size {
123   char *filename;
124   size_sum_t size;
125   struct file_with_size *next;
126 };
127
128 struct file_with_size *create(char *name, struct file_with_size *current) {
129   struct file_with_size *result;
130   result = malloc(sizeof(struct file_with_size));
131   result->filename = strdup(name);
132   result->size = file_or_dir_size(name);
133   result->next = current;
134   return result;
135 }
136
137 void destroy(struct file_with_size *node) {
138   struct file_with_size *next;
139   while(node) {
140     next = node->next;
141     free(node->filename);
142     free(node);
143     node = next;
144   }
145 }
146
147 /**********************************************************************/
148
149 int compare_files(const void *x1, const void *x2) {
150   const struct file_with_size **f1, **f2;
151
152   f1 = (const struct file_with_size **) x1;
153   f2 = (const struct file_with_size **) x2;
154
155   if(reverse_sorting) {
156     if((*f1)->size < (*f2)->size) {
157       return 1;
158     } else if((*f1)->size > (*f2)->size) {
159       return -1;
160     } else {
161       return 0;
162     }
163   } else {
164     if((*f1)->size < (*f2)->size) {
165       return -1;
166     } else if((*f1)->size > (*f2)->size) {
167       return 1;
168     } else {
169       return 0;
170     }
171   }
172 }
173
174 void print_sorted(struct file_with_size *root, int height) {
175   struct file_with_size *node;
176   struct file_with_size **nodes;
177   int nb, n, first, last;
178
179   nb = 0;
180   for(node = root; node; node = node->next) {
181     nb++;
182   }
183
184   nodes = malloc(nb * sizeof(struct file_with_size *));
185
186   n = 0;
187   for(node = root; node; node = node->next) {
188     nodes[n++] = node;
189   }
190
191   qsort(nodes, nb, sizeof(struct file_with_size *), compare_files);
192
193   first = 0;
194   last = nb;
195
196   if(forced_height) {
197     height = forced_height;
198   }
199
200   if(height > 0 && height < nb) {
201     first = nb - height;
202   }
203
204   if(show_top) {
205     n = last;
206     last = nb - first;
207     first = nb - n;
208   }
209
210   if(fancy_size_display) {
211     for(n = first; n < last; n++) {
212       if(nodes[n]->size < 1024) {
213         printf("% 7d %s\n",
214                ((int) nodes[n]->size),
215                nodes[n]->filename);
216       } else if(nodes[n]->size < 1024 * 1024) {
217         printf("% 6.1fK %s\n",
218                ((double) (nodes[n]->size))/(1024.0),
219                nodes[n]->filename);
220       } else if(nodes[n]->size < 1024 * 1024 * 1024) {
221         printf("% 6.1fM %s\n",
222                ((double) (nodes[n]->size))/(1024.0 * 1024),
223                nodes[n]->filename);
224       } else {
225         printf("% 6.1fG %s\n",
226                ((double) (nodes[n]->size))/(1024.0 * 1024.0 * 1024.0),
227                nodes[n]->filename);
228       }
229     }
230   } else {
231     for(n = first; n < last; n++) {
232       print_size_sum(nodes[n]->size);
233       printf(" %s\n", nodes[n]->filename);
234     }
235   }
236
237   free(nodes);
238 }
239
240 /**********************************************************************/
241
242 int main(int argc, char **argv) {
243   int c;
244   struct file_with_size *root;
245
246   root = 0;
247
248   setlocale (LC_ALL, "");
249
250   while (1) {
251     c = getopt(argc, argv, "dfrtl:hdu");
252     if (c == -1)
253       break;
254
255     switch (c) {
256
257     case 'd':
258       ignore_dotfiles = 1;
259       break;
260
261     case 'f':
262       fancy_size_display = 1;
263       break;
264
265     case 'r':
266       reverse_sorting = 1;
267       break;
268
269     case 't':
270       show_top = 1;
271       break;
272
273     case 'l':
274       forced_height = atoi(optarg);
275       break;
276
277     case 'h':
278       printf("Usage: dus [OPTION]... [FILE]...\n");
279       printf("List files and directories sorted according to their size or content size (the content of the current directory by default).\n");
280       printf("\n");
281       printf("   -d         ignore files and directories starting with a '.'\n");
282       printf("   -f         display size with float values and K, M and G units.\n");
283       printf("   -r         reverse the sorting order.\n");
284       printf("   -t         show the top of the list.\n");
285       printf("   -l <lines> specificy the number of lines to display. The value -1\n");
286       printf("              corresponds to all the lines. By default the command\n");
287       printf("              shows one line less than the height the tty, or all the\n");
288       printf("              lines if the standard output is not a tty.\n");
289       printf("   -h         show this help.\n");
290       printf("\n");
291       printf("Report bugs and comments to <francois@fleuret.org>\n");
292       exit(EXIT_SUCCESS);
293
294       break;
295
296     default:
297       exit(EXIT_FAILURE);
298     }
299   }
300
301   if (optind < argc) {
302     while (optind < argc) {
303       root = create(argv[optind++], root);
304     }
305   } else {
306     DIR *dir;
307     struct dirent *dir_e;
308     dir = opendir(".");
309     if(dir) {
310       while((dir_e = readdir(dir))) {
311         if(!ignore_entry(dir_e->d_name)) {
312           root = create(dir_e->d_name, root);
313         }
314       }
315       closedir(dir);
316     }
317   }
318
319   if(isatty(STDOUT_FILENO)) {
320     struct winsize win;
321     if(ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &win)) {
322       printf("Can not get the tty size: %s\n", strerror(errno));
323       exit (EXIT_FAILURE);
324     }
325     print_sorted(root, win.ws_row - 2);
326   } else {
327     print_sorted(root, -1);
328   }
329
330   destroy(root);
331
332   exit(EXIT_SUCCESS);
333 }