Initial commit.
[finddup.git] / finddup.c
1
2 /*
3  *  finddup 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 finddup.
10  *
11  *  finddup 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  *  finddup 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 finddup.  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 #include <fcntl.h>
39
40 #define BUFFER_SIZE 4096
41
42 typedef int64_t size_sum_t;
43
44 /* Yeah, global variables! */
45
46 int ignore_dotfiles = 0; /* 1 means ignore files and directories
47                             starting with a dot */
48
49 int forced_width = 0; /* -1 means no width limit, strictly positive
50                          means limit, 0 means not active */
51
52 int forced_height = 0; /* -1 means no height limit, strictly positive
53                            means limit, 0 means not active */
54
55 int fancy_size_display = 0; /* 1 means to use floating values with K, M and G
56                                as units */
57
58 int reverse_sorting = 0; /* 1 means to show the large ones first */
59
60 int show_top = 0; /* 1 means to show the top of the sorted list
61                      instead of the bottom */
62
63 /********************************************************************/
64
65 /* malloc with error checking.  */
66
67 void *safe_malloc(size_t n) {
68   void *p = malloc(n);
69   if (!p && n != 0) {
70     printf("Can not allocate memory: %s\n", strerror(errno));
71     exit(EXIT_FAILURE);
72   }
73   return p;
74 }
75
76 /********************************************************************/
77
78 int ignore_entry(const char *name) {
79   return
80     strcmp(name, ".") == 0 ||
81     strcmp(name, "..") == 0 ||
82     (ignore_dotfiles && name[0] == '.');
83 }
84
85 void print_size_sum(size_sum_t s) {
86   char tmp[100];
87   char *a = tmp + sizeof(tmp)/sizeof(char);
88   *(--a) = '\0';
89   if(s) {
90     while(s) {
91       *(--a) = s%10 + '0';
92       s /= 10;
93     }
94   } else {
95     *(--a) = '0';
96   }
97   printf(a);
98 }
99
100 size_sum_t file_or_dir_size(const char *name) {
101   DIR *dir;
102   struct dirent *dir_e;
103   struct stat dummy;
104   size_sum_t result;
105   char subname[BUFFER_SIZE];
106
107   result = 0;
108
109   if(lstat(name, &dummy) != 0) {
110     printf("Can not stat %s: %s\n", name, strerror(errno));
111     exit(EXIT_FAILURE);
112   }
113
114   if(S_ISLNK(dummy.st_mode)) {
115     return 0;
116   }
117
118   dir = opendir(name);
119
120   if(dir) {
121     while((dir_e = readdir(dir))) {
122       if(!ignore_entry(dir_e->d_name)) {
123         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
124         result += file_or_dir_size(subname);
125       }
126     }
127     closedir(dir);
128   } else {
129     if(S_ISREG(dummy.st_mode)) {
130       result += dummy.st_size;
131     }
132   }
133
134   return result;
135 }
136
137 /**********************************************************************/
138
139 struct file_with_size {
140   char *filename;
141   size_t size;
142   struct file_with_size *next;
143   int error;
144 };
145
146 struct file_list {
147   struct file_with_size *head, *tail;
148 };
149
150 void merge(struct *file_list list1, struct *file_list list2) {
151   if(list1->head && list2->head) {
152     struct file_list result;
153     result.head = list1.head;
154   } else {
155     if(list1.head) {
156       return list1;
157     } else {
158       return list2;
159     }
160   }
161 }
162
163 struct file_with_size *create(char *name, struct file_with_size *current) {
164   struct file_with_size *result;
165   result = safe_malloc(sizeof(struct file_with_size));
166   result->filename = strdup(name);
167   result->size = file_or_dir_size(name);
168   result->next = current;
169   return result;
170 }
171
172 void destroy(struct file_with_size *node) {
173   struct file_with_size *next;
174   while(node) {
175     next = node->next;
176     free(node->filename);
177     free(node);
178     node = next;
179   }
180 }
181
182 /**********************************************************************/
183
184 int same_size(struct file_with_size *f1, struct file_with_size *f2) {
185   return f1->size == f2->size;
186 }
187
188 int same_content(struct file_with_size *f1, struct file_with_size *f2) {
189   int fd1, fd2, eq, s1, s2;
190   char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
191
192   fd1 = open(f1->filename, O_RDONLY);
193   fd2 = open(f2->filename, O_RDONLY);
194   eq = 1;
195
196   if(fd1 >= 0 && fd2 >= 0) {
197     while(eq) {
198       s1 = read(fd1, buffer1, BUFFER_SIZE);
199       if(s1 < 0) {
200         f1->error = 1;
201         eq = 0;
202       }
203       s2 = read(fd2, buffer2, BUFFER_SIZE);
204       if(s2 < 0) {
205         f2->error = 1;
206         eq = 0;
207       }
208       if(s1 > 0 && s2 > 0) {
209         if(s1 == s2) {
210           eq = (strncmp(buffer1, buffer2, s1) == 0);
211         } else {
212           fprintf(stderr,
213                   "Different read size without error on files of same size.\n");
214           exit(EXIT_FAILURE);
215         }
216       }
217     }
218   }
219
220   if(fd1 >= 0) {
221     close(fd1);
222   } else {
223     f1->error = 1;
224     eq = 0;
225   }
226
227   if(fd2 >= 0) {
228     close(fd2);
229   } else {
230     f2->error = 1;
231     eq = 0;
232   }
233
234   return eq;
235 }
236
237 int same_files(struct file_with_size *f1, struct file_with_size *f2) {
238   return same_size(f1, f2) && same_content(f1, f2);
239 }
240
241 /**********************************************************************/
242
243 struct file_list scan_directory(const char *name) {
244   DIR *dir;
245   struct dirent *dir_e;
246   struct file_list result, sublist;
247   struct stat dummy;
248   char subname[BUFFER_SIZE];
249
250   result.head = 0;
251   result.tail = 0;
252
253   if(lstat(name, &dummy) != 0) {
254     fprintf(stderr, "Can not stat %s: %s\n", name, strerror(errno));
255     exit(EXIT_FAILURE);
256   }
257
258   if(S_ISLNK(dummy.st_mode)) {
259     return result;
260   }
261
262   dir = opendir(name);
263
264   if(dir) {
265     while((dir_e = readdir(dir))) {
266       if(!ignore_entry(dir_e->d_name)) {
267         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
268         sublist = scan_directory(subname);
269         result = concat(result, sublist);
270       }
271     }
272     closedir(dir);
273   } else {
274     if(S_ISREG(dummy.st_mode)) {
275       result += dummy.st_size;
276     }
277   }
278
279   return result;
280 }
281
282 void start(const char *dirname1, const char *dirname2) {
283   struct file_with_size *list1, *list2;
284   list1 = scan_directory(dirname1);
285   list2 = scan_directory(dirname2);
286   destroy(list1);
287   destroy(list2);
288 }
289
290 /**********************************************************************/
291
292 int main(int argc, char **argv) {
293   int c;
294   struct file_with_size *root;
295
296   root = 0;
297
298   setlocale (LC_ALL, "");
299
300   while (1) {
301     c = getopt(argc, argv, "h");
302     if (c == -1)
303       break;
304
305     switch (c) {
306
307     case 'h':
308       printf("Usage: finddup [OPTION]... [FILE]...\n");
309       printf("Report bugs and comments to <francois@fleuret.org>\n");
310       exit(EXIT_SUCCESS);
311
312       break;
313
314     default:
315       exit(EXIT_FAILURE);
316     }
317   }
318
319   if (optind + 2 < argc) {
320     start(argv[optind + 1], argv[optind + 2]);
321   } else {
322     fprintf(stderr, "%s [OPTIONS] <dir1> <dir2>\n", argv[0]);
323     exit(EXIT_FAILURE);
324   }
325
326   exit(EXIT_SUCCESS);
327 }