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