Cosmetics.
[finddup.git] / finddup.c
1
2 /*
3  *  finddup is a simple utility find duplicated files, files common to
4  *  several directories, or files present in one directory and not in
5  *  another one.
6  *
7  *  Copyright (c) 2010 Francois Fleuret
8  *  Written by Francois Fleuret <francois@fleuret.org>
9  *
10  *  This file is part of finddup.
11  *
12  *  finddup is free software: you can redistribute it and/or modify it
13  *  under the terms of the GNU General Public License version 3 as
14  *  published by the Free Software Foundation.
15  *
16  *  finddup is distributed in the hope that it will be useful, but WITHOUT
17  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18  *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
19  *  License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with finddup.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25
26 #define VERSION_NUMBER "0.5"
27
28 #define _BSD_SOURCE
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <dirent.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <unistd.h>
36 #include <errno.h>
37 #include <string.h>
38 #include <sys/ioctl.h>
39 #include <locale.h>
40 #include <getopt.h>
41 #include <fcntl.h>
42
43 #define BUFFER_SIZE 4096
44
45 typedef int64_t size_sum_t;
46
47 /* Yeah, global variables! */
48
49 int ignore_dotfiles = 0; /* 1 means ignore files and directories
50                             starting with a dot */
51
52 int show_realpaths = 0; /* 1 means ignore files and directories
53                             starting with a dot */
54
55 int show_progress = 1; /* 1 means show a progress bar when we are in a
56                           tty */
57
58 /********************************************************************/
59
60 /* malloc with error checking.  */
61
62 void *safe_malloc(size_t n) {
63   void *p = malloc(n);
64   if (!p && n != 0) {
65     printf("Can not allocate memory: %s\n", strerror(errno));
66     exit(EXIT_FAILURE);
67   }
68   return p;
69 }
70
71 /********************************************************************/
72
73 int ignore_entry(const char *name) {
74   return
75     strcmp(name, ".") == 0 ||
76     strcmp(name, "..") == 0 ||
77     (ignore_dotfiles && name[0] == '.');
78 }
79
80 void print_size_sum(size_sum_t s) {
81   char tmp[100];
82   char *a = tmp + sizeof(tmp)/sizeof(char);
83   *(--a) = '\0';
84   if(s) {
85     while(s) {
86       *(--a) = s%10 + '0';
87       s /= 10;
88     }
89   } else {
90     *(--a) = '0';
91   }
92   printf(a);
93 }
94
95 /**********************************************************************/
96
97 struct file_with_size {
98   char *filename;
99   size_t size;
100   ino_t inode;
101   struct file_with_size *next;
102   int error;
103 };
104
105 void file_list_delete(struct file_with_size *head) {
106   struct file_with_size *next;
107   while(head) {
108     next = head->next;
109     free(head->filename);
110     free(head);
111     head = next;
112   }
113 }
114
115 int file_list_length(struct file_with_size *head) {
116   int l = 0;
117   while(head) {
118     l++;
119     head = head->next;
120   }
121   return l;
122 }
123
124 /**********************************************************************/
125
126 int same_size(struct file_with_size *f1, struct file_with_size *f2) {
127   return f1->size == f2->size;
128 }
129
130 int same_content(struct file_with_size *f1, struct file_with_size *f2) {
131   int fd1, fd2, s1, s2;
132   char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
133
134   fd1 = open(f1->filename, O_RDONLY);
135   fd2 = open(f2->filename, O_RDONLY);
136
137   if(fd1 >= 0 && fd2 >= 0) {
138     while(1) {
139       s1 = read(fd1, buffer1, BUFFER_SIZE);
140       s2 = read(fd2, buffer2, BUFFER_SIZE);
141
142       if(s1 < 0 || s2 < 0) {
143         close(fd1);
144         close(fd2);
145         return 0;
146       }
147
148       if(s1 == s2) {
149         if(s1 == 0) {
150           close(fd1);
151           close(fd2);
152           return 1;
153         } else {
154           if(strncmp(buffer1, buffer2, s1)) {
155             close(fd1);
156             close(fd2);
157             return 0;
158           }
159         }
160       } else {
161         fprintf(stderr,
162                 "Different read size without error on files of same size.\n");
163         exit(EXIT_FAILURE);
164       }
165     }
166   } else {
167     if(fd1 >= 0) { close(fd1); }
168     if(fd2 >= 0) { close(fd2); }
169     return 0;
170   }
171 }
172
173 int same_files(struct file_with_size *f1, struct file_with_size *f2) {
174   return same_size(f1, f2) && same_content(f1, f2);
175 }
176
177 /**********************************************************************/
178
179 struct file_with_size *scan_directory(struct file_with_size *tail,
180                                       const char *name) {
181   DIR *dir;
182   struct dirent *dir_e;
183   struct stat dummy;
184   struct file_with_size *tmp;
185   char subname[PATH_MAX];
186
187   if(lstat(name, &dummy) != 0) {
188     fprintf(stderr, "Can not stat \"%s\": %s\n", name, strerror(errno));
189     exit(EXIT_FAILURE);
190   }
191
192   if(S_ISLNK(dummy.st_mode)) {
193     return tail;
194   }
195
196   dir = opendir(name);
197
198   if(dir) {
199     while((dir_e = readdir(dir))) {
200       if(!ignore_entry(dir_e->d_name)) {
201         snprintf(subname, PATH_MAX, "%s/%s", name, dir_e->d_name);
202         tail = scan_directory(tail, subname);
203       }
204     }
205     closedir(dir);
206   } else {
207     if(S_ISREG(dummy.st_mode)) {
208       tmp = safe_malloc(sizeof(struct file_with_size));
209       tmp->next = tail;
210       tmp->filename = strdup(name);
211       tmp->size = dummy.st_size;
212       tmp->inode = dummy.st_ino;
213       tail = tmp;
214     }
215   }
216
217   return tail;
218 }
219
220 void start(const char *dirname1, const char *dirname2) {
221   struct file_with_size *list1, *list2;
222   struct file_with_size *node1, *node2;
223   int not_in, found;
224
225   if(strncmp(dirname2, "not:", 4) == 0) {
226     not_in = 1;
227     dirname2 += 4;
228   } else {
229     not_in = 0;
230   }
231
232   list1 = scan_directory(0, dirname1);
233   list2 = scan_directory(0, dirname2);
234
235   if(not_in) {
236     for(node1 = list1; node1; node1 = node1->next) {
237       found = 0;
238
239       for(node2 = list2; !found && node2; node2 = node2->next) {
240         if(node1->inode != node2->inode && same_files(node1, node2)) {
241           found = 1;
242         }
243       }
244
245       if(!found) {
246         if(show_realpaths) {
247           printf("%s\n", realpath(node1->filename, 0));
248         } else {
249           printf("%s\n", node1->filename);
250         }
251       }
252     }
253
254   } else {
255
256     for(node1 = list1; node1; node1 = node1->next) {
257       for(node2 = list2; node2; node2 = node2->next) {
258         if(node1->inode != node2->inode && same_files(node1, node2)) {
259           if(show_realpaths) {
260             printf("%s %s\n",
261                    realpath(node1->filename, 0),
262                    realpath(node2->filename, 0));
263           } else {
264             printf("%s %s\n", node1->filename, node2->filename);
265           }
266         }
267       }
268     }
269   }
270
271   file_list_delete(list1);
272   file_list_delete(list2);
273 }
274
275 void print_help(FILE *out) {
276   fprintf(out, "Usage: finddup [OPTION]... DIR1 [[not:]DIR2]\n");
277   fprintf(out, "Version %s (%s)\n", VERSION_NUMBER, UNAME);
278   fprintf(out, "Without DIR2, lists duplicated files found in DIR1. With DIR2, lists files common to both directories. With the not: prefix, lists files found in DIR1 which do not exist in DIR2.\n");
279   fprintf(out, "\n");
280   fprintf(out, "   -h   show this help.\n");
281   fprintf(out, "   -r   show the real file paths.\n");
282   fprintf(out, "\n");
283   fprintf(out, "Report bugs and comments to <francois@fleuret.org>\n");
284 }
285
286 /**********************************************************************/
287
288 int main(int argc, char **argv) {
289   int c;
290   struct file_with_size *root;
291
292   root = 0;
293
294   setlocale (LC_ALL, "");
295
296   while (1) {
297     c = getopt(argc, argv, "hr");
298     if (c == -1)
299       break;
300
301     switch (c) {
302
303     case 'h':
304       print_help(stdout);
305       exit(EXIT_SUCCESS);
306
307       break;
308
309     case 'r':
310       show_realpaths = 1;
311       break;
312
313     default:
314       exit(EXIT_FAILURE);
315     }
316   }
317
318   if(optind + 1 < argc) {
319     start(argv[optind], argv[optind + 1]);
320   } else if(optind < argc) {
321     start(argv[optind], argv[optind]);
322   } else {
323     print_help(stderr);
324     exit(EXIT_FAILURE);
325   }
326
327   exit(EXIT_SUCCESS);
328 }