Added the information from uname in the help.
[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 VERSION_NUMBER "1.0alpha"
26
27 #define _BSD_SOURCE
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <dirent.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <sys/ioctl.h>
38 #include <locale.h>
39 #include <getopt.h>
40
41 #define BUFFER_SIZE 4096
42
43 typedef int64_t size_sum_t;
44
45 /* Yeah, global variables! */
46
47 int ignore_dotfiles = 0; /* 1 means ignore files and directories
48                             starting with a dot */
49
50 int forced_width = 0; /* -1 means no width limit, strictly positive
51                          means limit, 0 means not active */
52
53 int forced_height = 0; /* -1 means no height limit, strictly positive
54                            means limit, 0 means not active */
55
56 int fancy_size_display = 0; /* 1 means to use floating values with K, M and G
57                                as units */
58
59 int reverse_sorting = 0; /* 1 means to show the large ones first */
60
61 int show_top = 0; /* 1 means to show the top of the sorted list
62                      instead of the bottom */
63
64 /********************************************************************/
65
66 /* malloc with error checking.  */
67
68 void *safe_malloc(size_t n) {
69   void *p = malloc(n);
70   if (!p && n != 0) {
71     fprintf(stderr, "Can not allocate memory: %s\n", strerror(errno));
72     exit(EXIT_FAILURE);
73   }
74   return p;
75 }
76
77 /********************************************************************/
78
79 int ignore_entry(const char *name) {
80   return
81     strcmp(name, ".") == 0 ||
82     strcmp(name, "..") == 0 ||
83     (ignore_dotfiles && name[0] == '.');
84 }
85
86 void print_size_sum(size_sum_t s) {
87   char tmp[100];
88   char *a = tmp + sizeof(tmp)/sizeof(char);
89   *(--a) = '\0';
90   if(s) {
91     while(s) {
92       *(--a) = s%10 + '0';
93       s /= 10;
94     }
95   } else {
96     *(--a) = '0';
97   }
98   printf(a);
99 }
100
101 size_sum_t file_or_dir_size(const char *name) {
102   DIR *dir;
103   struct dirent *dir_e;
104   struct stat dummy;
105   size_sum_t result;
106   char subname[BUFFER_SIZE];
107
108   result = 0;
109
110   if(lstat(name, &dummy) != 0) {
111     fprintf(stderr, "Can not stat %s: %s\n", name, strerror(errno));
112     exit(EXIT_FAILURE);
113   }
114
115   if(S_ISLNK(dummy.st_mode)) {
116     return 0;
117   }
118
119   dir = opendir(name);
120
121   if(dir) {
122     while((dir_e = readdir(dir))) {
123       if(!ignore_entry(dir_e->d_name)) {
124         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
125         result += file_or_dir_size(subname);
126       }
127     }
128     closedir(dir);
129   } else {
130     if(S_ISREG(dummy.st_mode)) {
131       result += dummy.st_size;
132     }
133   }
134
135   return result;
136 }
137
138 /**********************************************************************/
139
140 struct file_with_size {
141   char *filename;
142   size_sum_t size;
143   struct file_with_size *next;
144 };
145
146 struct file_with_size *create(char *name, struct file_with_size *current) {
147   struct file_with_size *result;
148   result = safe_malloc(sizeof(struct file_with_size));
149   result->filename = strdup(name);
150   result->size = file_or_dir_size(name);
151   result->next = current;
152   return result;
153 }
154
155 void destroy(struct file_with_size *node) {
156   struct file_with_size *next;
157   while(node) {
158     next = node->next;
159     free(node->filename);
160     free(node);
161     node = next;
162   }
163 }
164
165 /**********************************************************************/
166
167 int compare_files(const void *x1, const void *x2) {
168   const struct file_with_size **f1, **f2;
169
170   f1 = (const struct file_with_size **) x1;
171   f2 = (const struct file_with_size **) x2;
172
173   if(reverse_sorting) {
174     if((*f1)->size < (*f2)->size) {
175       return 1;
176     } else if((*f1)->size > (*f2)->size) {
177       return -1;
178     } else {
179       return 0;
180     }
181   } else {
182     if((*f1)->size < (*f2)->size) {
183       return -1;
184     } else if((*f1)->size > (*f2)->size) {
185       return 1;
186     } else {
187       return 0;
188     }
189   }
190 }
191
192 void raw_print(char *buffer, char *filename,  size_sum_t size) {
193   char *a, *b, *c, u;
194
195   b = buffer;
196   if(size) {
197     while(size) {
198       *(b++) = size%10 + '0';
199       size /= 10;
200     }
201   } else {
202     *(b++) = '0';
203   }
204
205   a = buffer;
206   c = b;
207   while(a < c) {
208     u = *a;
209     *(a++) = *(--c);
210     *c = u;
211   }
212
213   *(b++) = ' ';
214
215   sprintf(b, " %s\n", filename);
216 }
217
218 void fancy_print(char *buffer, char *filename, size_sum_t size) {
219   if(size < 1024) {
220     sprintf(buffer,
221             "% 7d %s\n",
222             ((int) size),
223             filename);
224   } else if(size < 1024 * 1024) {
225     sprintf(buffer,
226             "% 6.1fK %s\n",
227             ((double) (size))/(1024.0),
228             filename);
229   } else if(size < 1024 * 1024 * 1024) {
230     sprintf(buffer,
231             "% 6.1fM %s\n",
232             ((double) (size))/(1024.0 * 1024),
233             filename);
234   } else {
235     sprintf(buffer,
236             "% 6.1fG %s\n",
237             ((double) (size))/(1024.0 * 1024.0 * 1024.0),
238             filename);
239   }
240 }
241
242 void print_sorted(struct file_with_size *root, int width, int height) {
243   char line[BUFFER_SIZE];
244   struct file_with_size *node;
245   struct file_with_size **nodes;
246   int nb, n, first, last;
247
248   nb = 0;
249   for(node = root; node; node = node->next) {
250     nb++;
251   }
252
253   nodes = safe_malloc(nb * sizeof(struct file_with_size *));
254
255   n = 0;
256   for(node = root; node; node = node->next) {
257     nodes[n++] = node;
258   }
259
260   qsort(nodes, nb, sizeof(struct file_with_size *), compare_files);
261
262   first = 0;
263   last = nb;
264
265   if(forced_height) {
266     height = forced_height;
267   }
268
269   if(height > 0 && height < nb) {
270     first = nb - height;
271   }
272
273   if(show_top) {
274     n = last;
275     last = nb - first;
276     first = nb - n;
277   }
278
279   for(n = first; n < last; n++) {
280     if(fancy_size_display) {
281       fancy_print(line, nodes[n]->filename, nodes[n]->size);
282     } else {
283       raw_print(line, nodes[n]->filename, nodes[n]->size);
284     }
285     if(width >= 0 && width < BUFFER_SIZE) {
286       line[width] = '\0';
287     }
288     printf(line);
289   }
290
291   free(nodes);
292 }
293 /**********************************************************************/
294 void print_help(FILE *out) {
295   fprintf(out, "Usage: dus [OPTION]... [FILE]...\n");
296   fprintf(out, "Version %s (%s)\n", VERSION_NUMBER, UNAME);
297   fprintf(out, "List files and directories sorted according to their size or content size. Take the content of the current directory as argument if none is provided.\n");
298   fprintf(out, "\n");
299   fprintf(out, "   -d          ignore files and directories starting with a '.'\n");
300   fprintf(out, "   -f          display size with float values and K, M and G units.\n");
301   fprintf(out, "   -r          reverse the sorting order.\n");
302   fprintf(out, "   -t          show the top of the list.\n");
303   fprintf(out, "   -c <cols>   specificy the number of columns to display. The value -1\n");
304   fprintf(out, "               corresponds to no constraint. By default the command\n");
305   fprintf(out, "               uses the tty width, or no constraint if the stdout is\n");
306   fprintf(out, "               not a tty.\n");
307   fprintf(out, "   -l <lines>  same as -c for number of lines.\n");
308   fprintf(out, "   -h          show this help.\n");
309   fprintf(out, "\n");
310   fprintf(out, "Report bugs and comments to <francois@fleuret.org>\n");
311 }
312
313 /**********************************************************************/
314
315 int main(int argc, char **argv) {
316   int c;
317   struct file_with_size *root;
318
319   root = 0;
320
321   setlocale (LC_ALL, "");
322
323   while (1) {
324     c = getopt(argc, argv, "dfrtl:c:hdu");
325     if (c == -1)
326       break;
327
328     switch (c) {
329
330     case 'd':
331       ignore_dotfiles = 1;
332       break;
333
334     case 'f':
335       fancy_size_display = 1;
336       break;
337
338     case 'r':
339       reverse_sorting = 1;
340       break;
341
342     case 't':
343       show_top = 1;
344       break;
345
346     case 'l':
347       forced_height = atoi(optarg);
348       break;
349
350     case 'c':
351       forced_width = atoi(optarg);
352       break;
353
354     case 'h':
355       print_help(stdout);
356       exit(EXIT_SUCCESS);
357
358       break;
359
360     default:
361       print_help(stderr);
362       exit(EXIT_FAILURE);
363     }
364   }
365
366   if (optind < argc) {
367     while (optind < argc) {
368       root = create(argv[optind++], root);
369     }
370   } else {
371     DIR *dir;
372     struct dirent *dir_e;
373     dir = opendir(".");
374     if(dir) {
375       while((dir_e = readdir(dir))) {
376         if(!ignore_entry(dir_e->d_name)) {
377           root = create(dir_e->d_name, root);
378         }
379       }
380       closedir(dir);
381     } else {
382       fprintf(stderr, "Can not open ./: %s\n", strerror(errno));
383       exit (EXIT_FAILURE);
384     }
385   }
386
387   if(isatty(STDOUT_FILENO)) {
388     struct winsize win;
389     if(ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &win)) {
390       fprintf(stderr, "Can not get the tty size: %s\n", strerror(errno));
391       exit (EXIT_FAILURE);
392     }
393     print_sorted(root, win.ws_col, win.ws_row - 2);
394   } else {
395     print_sorted(root, -1, -1);
396   }
397
398   destroy(root);
399
400   exit(EXIT_SUCCESS);
401 }