First working version. When sizes match, runs full comparison.
[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 /**********************************************************************/
101
102 struct file_with_size {
103   char *filename;
104   size_t size;
105   ino_t inode;
106   struct file_with_size *next;
107   int error;
108 };
109
110 void file_list_delete(struct file_with_size *head) {
111   struct file_with_size *next;
112   while(head) {
113     next = head->next;
114     free(head->filename);
115     free(head);
116     head = next;
117   }
118 }
119
120 int file_list_length(struct file_with_size *head) {
121   int l = 0;
122   while(head) {
123     l++;
124     head = head->next;
125   }
126   return l;
127 }
128
129 /**********************************************************************/
130
131 int same_size(struct file_with_size *f1, struct file_with_size *f2) {
132   return f1->size == f2->size;
133 }
134
135 int same_content(struct file_with_size *f1, struct file_with_size *f2) {
136   int fd1, fd2, s1, s2;
137   char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
138
139   fd1 = open(f1->filename, O_RDONLY);
140   fd2 = open(f2->filename, O_RDONLY);
141
142   if(fd1 >= 0 && fd2 >= 0) {
143     while(1) {
144       s1 = read(fd1, buffer1, BUFFER_SIZE);
145       s2 = read(fd2, buffer2, BUFFER_SIZE);
146
147       if(s1 < 0 || s2 < 0) {
148         close(fd1);
149         close(fd2);
150         return 0;
151       }
152
153       if(s1 == s2) {
154         if(s1 == 0) {
155           return 1;
156         } else {
157           if(strncmp(buffer1, buffer2, s1)) {
158             close(fd1);
159             close(fd2);
160             return 0;
161           }
162         }
163       } else {
164         fprintf(stderr,
165                 "Different read size without error on files of same size.\n");
166         exit(EXIT_FAILURE);
167       }
168     }
169   } else {
170     return 0;
171   }
172 }
173
174 int same_files(struct file_with_size *f1, struct file_with_size *f2) {
175   return same_size(f1, f2) && same_content(f1, f2);
176 }
177
178 /**********************************************************************/
179
180 struct file_with_size *scan_directory(struct file_with_size *tail,
181                                       const char *name) {
182   DIR *dir;
183   struct dirent *dir_e;
184   struct stat dummy;
185   struct file_with_size *tmp;
186   char subname[BUFFER_SIZE];
187
188   if(lstat(name, &dummy) != 0) {
189     fprintf(stderr, "Can not stat %s: %s\n", name, strerror(errno));
190     exit(EXIT_FAILURE);
191   }
192
193   if(S_ISLNK(dummy.st_mode)) {
194     return tail;
195   }
196
197   dir = opendir(name);
198
199   if(dir) {
200     while((dir_e = readdir(dir))) {
201       if(!ignore_entry(dir_e->d_name)) {
202         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
203         tail = scan_directory(tail, subname);
204       }
205     }
206     closedir(dir);
207   } else {
208     if(S_ISREG(dummy.st_mode)) {
209       tmp = safe_malloc(sizeof(struct file_with_size));
210       tmp->next = tail;
211       tmp->filename = strdup(name);
212       tmp->size = dummy.st_size;
213       tmp->inode = dummy.st_ino;
214       tail = tmp;
215     }
216   }
217
218   return tail;
219 }
220
221 void start(const char *dirname1, const char *dirname2) {
222   struct file_with_size *list1, *list2;
223   struct file_with_size *node1, *node2;
224   list1 = scan_directory(0, dirname1);
225   list2 = scan_directory(0, dirname2);
226
227   for(node1 = list1; node1; node1 = node1->next) {
228     for(node2 = list2; node2; node2 = node2->next) {
229       if(node1->inode != node2->inode && same_files(node1, node2)) {
230         printf("%s %s \n", node1->filename, node2->filename);
231       }
232     }
233   }
234
235   file_list_delete(list1);
236   file_list_delete(list2);
237 }
238
239 /**********************************************************************/
240
241 int main(int argc, char **argv) {
242   int c;
243   struct file_with_size *root;
244
245   root = 0;
246
247   setlocale (LC_ALL, "");
248
249   while (1) {
250     c = getopt(argc, argv, "h");
251     if (c == -1)
252       break;
253
254     switch (c) {
255
256     case 'h':
257       printf("Usage: finddup [OPTION]... [FILE]...\n");
258       printf("Report bugs and comments to <francois@fleuret.org>\n");
259       exit(EXIT_SUCCESS);
260
261       break;
262
263     default:
264       exit(EXIT_FAILURE);
265     }
266   }
267
268   if(optind + 1 < argc) {
269     start(argv[optind], argv[optind + 1]);
270   } else {
271     fprintf(stderr, "%s [OPTIONS] <dir1> <dir2>\n", argv[0]);
272     exit(EXIT_FAILURE);
273   }
274
275   exit(EXIT_SUCCESS);
276 }