Same typo in the help and the man page.
[finddup.git] / finddup.c
1
2 /*
3  *  finddup is a simple utility to find duplicated files, files common
4  *  to several directories, or files present in one directory and not
5  *  in another one.
6  *
7  *  Copyright (c) 2010, 2011 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 "1.2"
27
28 #define _BSD_SOURCE
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/param.h>
33 #include <sys/wait.h>
34 #include <dirent.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <errno.h>
39 #include <string.h>
40 #include <sys/ioctl.h>
41 #include <locale.h>
42 #include <getopt.h>
43 #include <fcntl.h>
44
45 /* 1M really helps compared to 64k */
46 #define READ_BUFFER_SIZE (1024 * 1024)
47
48 #define PROGRESS_BUFFER_SIZE 1024
49
50 typedef int64_t size_sum_t;
51
52 /* Yeah, global variables! */
53
54 int ignore_dotfiles = 0; /* 1 means ignore files and directories
55                             starting with a dot */
56
57 int ignore_empty_files = 0; /* 1 means ignore empty files */
58
59 int show_realpaths = 0; /* 1 means ignore files and directories
60                             starting with a dot */
61
62 int show_progress = 0; /* 1 means show a progress bar when we are in a
63                           tty */
64
65 int show_hits = 1; /* 1 means to show the files from dir2
66                       corresponding to the ones from dir1 */
67
68 int show_groups = 1; /* 1 means to show the group IDs when printing
69                         file names */
70
71 int same_inodes_are_different = 0; /* 1 means that comparison between
72                                       two files with same inode will
73                                       always be false */
74
75 int sort_by_time = 0; /* 1 means to sort files in each group according
76                          to the modification time */
77
78 char *command_to_exec = 0; /* the name of the command to exec for each
79                               group of identical files */
80
81 char *result_file_prefix = 0; /* The prefix to use to write result */
82
83 /********************************************************************/
84
85 /* malloc with error checking.  */
86
87 void *safe_malloc(size_t n) {
88   void *p = malloc(n);
89   if (!p && n != 0) {
90     fprintf(stderr,
91             "finddup: Can not allocate memory: %s\n", strerror(errno));
92     exit(EXIT_FAILURE);
93   }
94   return p;
95 }
96
97 /********************************************************************/
98
99 int ignore_entry(const char *name) {
100   return
101     strcmp(name, ".") == 0 ||
102     strcmp(name, "..") == 0 ||
103     (ignore_dotfiles && name[0] == '.' && name[1] != '/');
104 }
105
106 /**********************************************************************/
107
108 struct file_node {
109   struct file_node *next;
110   char *name;
111   size_t size;
112   time_t atime, mtime, ctime;
113   ino_t inode;
114   int group_id; /* one per identical file content */
115   int dir_id; /* 1 for DIR1, and 2 for DIR2 */
116 };
117
118 void file_list_delete(struct file_node *head) {
119   struct file_node *next;
120   while(head) {
121     next = head->next;
122     free(head->name);
123     free(head);
124     head = next;
125   }
126 }
127
128 int file_list_length(struct file_node *head) {
129   int l = 0;
130   while(head) {
131     l++;
132     head = head->next;
133   }
134   return l;
135 }
136
137 /**********************************************************************/
138
139 int same_content(struct file_node *f1, struct file_node *f2,
140                  char *buffer1, char *buffer2) {
141   int fd1, fd2, s1, s2;
142
143   fd1 = open(f1->name, O_RDONLY);
144   fd2 = open(f2->name, O_RDONLY);
145
146   if(fd1 >= 0 && fd2 >= 0) {
147     while(1) {
148       s1 = read(fd1, buffer1, READ_BUFFER_SIZE);
149       s2 = read(fd2, buffer2, READ_BUFFER_SIZE);
150
151       if(s1 < 0 || s2 < 0) {
152         close(fd1);
153         close(fd2);
154         return 0;
155       }
156
157       if(s1 == s2) {
158         if(s1 == 0) {
159           close(fd1);
160           close(fd2);
161           return 1;
162         } else {
163           if(memcmp(buffer1, buffer2, s1)) {
164             /* printf("size_to_read = %d\n", size_to_read); */
165             close(fd1);
166             close(fd2);
167             return 0;
168           }
169         }
170       } else {
171         fprintf(stderr,
172                 "finddup: Different read size without error on files of same size.\n");
173         exit(EXIT_FAILURE);
174       }
175     }
176   } else {
177
178     if(fd1 < 0) {
179       fprintf(stderr,
180               "finddup: Can not open \"%s\" error: %s\n",
181               f1->name,
182               strerror(errno));
183     }
184
185     if(fd2 < 0) {
186       fprintf(stderr,
187               "finddup: Can not open \"%s\" error: %s\n",
188               f2->name,
189               strerror(errno));
190     }
191
192     exit(EXIT_FAILURE);
193   }
194 }
195
196 int same_files(struct file_node *f1, struct file_node *f2,
197                char *buffer1, char *buffer2) {
198   if(same_inodes_are_different && f1->inode == f2->inode) {
199     return 0;
200   }
201
202   return f1->size == f2->size && same_content(f1, f2, buffer1, buffer2);
203 }
204
205 /**********************************************************************/
206
207 struct file_node *scan_directory_rec(struct file_node *tail, const char *name) {
208   DIR *dir;
209   struct dirent *dir_e;
210   struct stat sb;
211   struct file_node *tmp;
212   char subname[PATH_MAX + 1];
213
214   if(lstat(name, &sb) != 0) {
215     fprintf(stderr, "finddup: Can not stat \"%s\": %s\n", name, strerror(errno));
216     exit(EXIT_FAILURE);
217   }
218
219   if(S_ISLNK(sb.st_mode)) {
220     return tail;
221   }
222
223   dir = opendir(name);
224
225   if(dir) {
226     while((dir_e = readdir(dir))) {
227       if(!ignore_entry(dir_e->d_name)) {
228         snprintf(subname, PATH_MAX, "%s/%s", name, dir_e->d_name);
229         tail = scan_directory_rec(tail, subname);
230       }
231     }
232     closedir(dir);
233   } else {
234     if(S_ISREG(sb.st_mode)) {
235       if(!ignore_entry(name)) {
236         if(!ignore_empty_files || sb.st_size > 0) {
237           tmp = safe_malloc(sizeof(struct file_node));
238           tmp->next = tail;
239           tmp->name = strdup(name);
240           tmp->size = sb.st_size;
241           tmp->atime = sb.st_atime;
242           tmp->mtime = sb.st_mtime;
243           tmp->ctime = sb.st_ctime;
244           tmp->inode = sb.st_ino;
245           tmp->group_id = -1;
246           tmp->dir_id = -1;
247           tail = tmp;
248         }
249       }
250     }
251   }
252
253   return tail;
254 }
255
256 struct file_node *scan_directory(struct file_node *tail, const char *name) {
257   struct file_node *result;
258   int length;
259
260   if(show_progress) {
261     fprintf(stderr, "Scanning '%s' ... ", name);
262   }
263
264   result = scan_directory_rec(tail, name);
265   length = file_list_length(result);
266
267   if(show_progress) {
268     fprintf(stderr, "done (%d file%s).\n",
269             length, (length > 1 ? "s" : ""));
270   }
271
272
273   return result;
274 }
275
276 void print_file(FILE *out, struct file_node *node) {
277   char tmp[PATH_MAX + 1];
278   if(show_realpaths) {
279     if(realpath(node->name, tmp)) {
280       if(show_groups) {
281         fprintf(out, "%d %s\n", node->group_id, tmp);
282       } else {
283         fprintf(out, "%s\n", tmp);
284       }
285     } else {
286       fprintf(stderr,
287               "finddup: Can not get the realpath of \"%s\": %s\n",
288               node->name,
289               strerror(errno));
290       exit(EXIT_FAILURE);
291     }
292   } else {
293     if(show_groups) {
294       fprintf(out, "%d %s\n", node->group_id, node->name);
295     } else {
296       fprintf(out, "%s\n", node->name);
297     }
298   }
299 }
300
301 int compare_nodes(const void *x1, const void *x2) {
302   const struct file_node **f1, **f2;
303
304   f1 = (const struct file_node **) x1;
305   f2 = (const struct file_node **) x2;
306
307   if((*f1)->group_id < (*f2)->group_id) {
308     return -1;
309   } else if((*f1)->group_id > (*f2)->group_id) {
310     return 1;
311   } else {
312     if(sort_by_time) {
313       if((*f1)->mtime < (*f2)->mtime) {
314         return -1;
315       } else if((*f1)->mtime > (*f2)->mtime) {
316         return 1;
317       } else {
318         return 0;
319       }
320     } else {
321       if((*f1)->dir_id < (*f2)->dir_id) {
322         return -1;
323       } else if((*f1)->dir_id > (*f2)->dir_id) {
324         return 1;
325       } else {
326         return 0;
327       }
328     }
329   }
330 }
331
332 void exec_command(int nb, struct file_node **nodes) {
333   char **args;
334   int max_group_size = 0, group_size, m, n, status;
335   pid_t pid;
336
337   for(n = 0; n < nb; n++) {
338     if(n == 0 || nodes[n]->group_id != nodes[n-1]->group_id) {
339       group_size = 0;
340     }
341     group_size++;
342     if(group_size > max_group_size) {
343       max_group_size = group_size;
344     }
345   }
346
347   args = safe_malloc((max_group_size + 2) * sizeof(char *));
348   args[0] = command_to_exec;
349
350   n = 0;
351   while(n < nb) {
352     m = n;
353     while(n < nb && nodes[n]->group_id == nodes[m]->group_id) {
354       args[n - m + 1] = nodes[n]->name;
355       n++;
356     }
357     args[n - m + 1] = 0;
358     pid = fork();
359     if(pid < 0) {
360     } else if(pid == 0) {
361       if(execvp(command_to_exec, args) < 0) {
362         perror("execvp");
363         exit(EXIT_FAILURE);
364       }
365     } else {
366       while(wait(&status) != pid);
367       if(status > 0) {
368         exit(EXIT_FAILURE);
369       }
370     }
371   }
372
373   free(args);
374 }
375
376 void write_groups_in_files(int nb, struct file_node **nodes) {
377   FILE *file = 0;
378   int current_group = -1, n;
379   char filename[PATH_MAX + 1];
380
381   for(n = 0; n < nb; n++) {
382     if(nodes[n]->group_id != current_group) {
383       if(file) { fclose(file); }
384       sprintf(filename, "%s%06d", result_file_prefix, nodes[n]->group_id);
385       file = fopen(filename, "w");
386       current_group = nodes[n]->group_id;
387       printf("Writing %s.\n" , filename);
388     }
389     print_file(file, nodes[n]);
390   }
391
392   if(file) { fclose(file); }
393 }
394
395 void print_result(struct file_node *list1, struct file_node *list2) {
396   struct file_node *node1, *node2;
397   struct file_node **nodes;
398   int nb, n;
399
400   nb = 0;
401   for(node1 = list1; node1; node1 = node1->next) {
402     if(node1->group_id >= 0) { nb++; }
403   }
404
405   if(list2) {
406     for(node2 = list2; node2; node2 = node2->next) {
407       if(node2->group_id >= 0) { nb++; }
408     }
409   }
410
411   nodes = safe_malloc(nb * sizeof(struct file_node *));
412
413   n = 0;
414   for(node1 = list1; node1; node1 = node1->next) {
415     if(node1->group_id >= 0) {
416       nodes[n++] = node1;
417     }
418   }
419
420   if(list2) {
421     for(node2 = list2; node2; node2 = node2->next) {
422       if(node2->group_id >= 0) {
423         nodes[n++] = node2;
424       }
425     }
426   }
427
428   qsort(nodes, nb, sizeof(struct file_node *), compare_nodes);
429
430   if(command_to_exec) {
431     exec_command(nb, nodes);
432   } else if(result_file_prefix) {
433     write_groups_in_files(nb, nodes);
434   } else {
435     for(n = 0; n < nb; n++) {
436       if(!show_groups && n > 0 && nodes[n]->group_id != nodes[n-1]->group_id) {
437         printf("\n");
438       }
439       print_file(stdout, nodes[n]);
440     }
441   }
442
443   free(nodes);
444 }
445
446 struct progress_state {
447   int bar_width;
448   int nb_values, value;
449   int last_position;
450 };
451
452 void print_progress(struct progress_state *state) {
453   int position, k, normalizer;
454   struct winsize win;
455   char buffer[PROGRESS_BUFFER_SIZE];
456   char *s;
457
458   normalizer = (state->nb_values > 1 ? state->nb_values - 1 : 1);
459
460   if(show_progress) {
461     /* We use the previous bar_width to compute the position, so that
462        we avoid doing too many ioctls */
463     position = (state->bar_width * state->value) / normalizer;
464     if(state->bar_width <= 0 || position != state->last_position) {
465       if(!ioctl (STDERR_FILENO, TIOCGWINSZ, (char *) &win)) {
466         /* Something weird is going on if the previous test is wrong */
467         if(win.ws_col >= PROGRESS_BUFFER_SIZE - 3) {
468           state->bar_width = PROGRESS_BUFFER_SIZE - 10;
469         } else {
470           state->bar_width = win.ws_col - 7;
471         }
472         position = (state->bar_width * state->value) / normalizer;
473         state->last_position = position;
474         s = buffer;
475         for(k = 0; k < position; k++) {
476           *(s++) = '+';
477         }
478         for(; k < state->bar_width; k++) {
479           *(s++) = '-';
480         }
481
482         /* We need four % because of the fprintf that follows */
483         sprintf(s, " [%3d%%]\r",
484                 (100 * state->value) / normalizer);
485
486         fprintf(stderr, "%s", buffer);
487       }
488     }
489   }
490 }
491
492 void start(const char *dirname1, const char *dirname2) {
493   struct file_node *list1, *list2;
494   struct file_node *node1, *node2;
495   struct progress_state progress_state;
496   int not_in, found;
497   int nb_groups, nb_nodes;
498   int list1_length, previous_progress;
499
500   char *buffer1 = safe_malloc(sizeof(char) * READ_BUFFER_SIZE);
501   char *buffer2 = safe_malloc(sizeof(char) * READ_BUFFER_SIZE);
502
503   not_in = 0;
504
505   list1 = scan_directory(0, dirname1);
506   list1_length = file_list_length(list1);
507
508   if(dirname2) {
509     if(strncmp(dirname2, "not:", 4) == 0) {
510       not_in = 1;
511       /* groups are not computed in the not: mode */
512       show_groups = 0;
513       dirname2 += 4;
514     } else if(strncmp(dirname2, "and:", 4) == 0) {
515       dirname2 += 4;
516     }
517     list2 = scan_directory(0, dirname2);
518   } else {
519     list2 = list1;
520   }
521
522   if(show_progress) {
523     fprintf(stderr,
524             "Now looking for identical files (this may take a while).\n");
525   }
526
527   nb_groups = 0;
528   previous_progress = -1;
529   nb_nodes = 0;
530
531   progress_state.bar_width = -1;
532   progress_state.last_position = -1;
533   progress_state.nb_values = list1_length;
534
535   if(not_in) {
536     for(node1 = list1; node1; node1 = node1->next) {
537       progress_state.value = nb_nodes;
538       print_progress(&progress_state);
539       nb_nodes++;
540
541       found = 0;
542
543       for(node2 = list2; !found && node2; node2 = node2->next) {
544         if(same_files(node1, node2, buffer1, buffer2)) {
545           found = 1;
546         }
547       }
548
549       if(!found) {
550         if(show_realpaths) {
551           printf("%s\n", realpath(node1->name, 0));
552         } else {
553           printf("%s\n", node1->name);
554         }
555       }
556     }
557
558   } else {
559     for(node1 = list1; node1; node1 = node1->next) {
560       progress_state.value = nb_nodes;
561       print_progress(&progress_state);
562       nb_nodes++;
563
564       for(node2 = list2; node2; node2 = node2->next) {
565         if(node1->group_id < 0 || node2->group_id < 0) {
566           if(same_files(node1, node2, buffer1, buffer2)) {
567             if(node1->group_id < 0) {
568               if(node2->group_id >= 0) {
569                 node1->group_id = node2->group_id;
570               } else {
571                 node1->group_id = nb_groups;
572                 node1->dir_id = 1;
573                 nb_groups++;
574               }
575             }
576             if(node2->group_id < 0) {
577               node2->group_id = node1->group_id;
578               node2->dir_id = 2;
579             }
580           }
581         }
582       }
583     }
584   }
585
586   if(show_progress) {
587     fprintf(stderr, "\n");
588   }
589
590   if(dirname2) {
591     print_result(list1, list2);
592     file_list_delete(list1);
593     file_list_delete(list2);
594   } else {
595     print_result(list1, 0);
596     file_list_delete(list1);
597   }
598
599   free(buffer1);
600   free(buffer2);
601 }
602
603 void usage(FILE *out) {
604   fprintf(out, "Usage: finddup [OPTION]... [DIR1 [[and:|not:]DIR2]]\n");
605   fprintf(out, "Version %s (%s)\n", VERSION_NUMBER, UNAME);
606   fprintf(out, "Without DIR2, lists duplicated files found in DIR1, or the current directory if DIR1 is not provided. With DIR2, lists files common to both directories. With the not: prefix, lists files found in DIR1 which do not exist in DIR2. The and: prefix is the default and should be used only if you have a directory starting with 'not:'\n");
607   fprintf(out, "\n");
608   /*            01234567890123456789012345678901234567890123456789012345678901234567890123456789*/
609   fprintf(out, "   -v, --version              prints the version number and exit\n");
610   fprintf(out, "   -h, --help                 show this help\n");
611   fprintf(out, "   -d, --ignore-dots          ignore dot files and directories\n");
612   fprintf(out, "   -0, --ignore-empty         ignore empty files\n");
613   fprintf(out, "   -c, --hide-matchings       do not show which files in DIR2 corresponds to\n");
614   fprintf(out, "                              those in DIR1\n");
615   fprintf(out, "   -g, --no-group-ids         do not show the file groups\n");
616   fprintf(out, "   -t, --time-sort            sort according to modification time in each group\n");
617   fprintf(out, "   -p, --show-progress        show progress\n");
618   fprintf(out, "   -r, --real-paths           show the real file paths\n");
619   fprintf(out, "   -i, --same-inodes-are-different\n");
620   fprintf(out, "                              consider files with same inode as different\n");
621   fprintf(out, "   -e <command>, --exec <command>\n");
622   fprintf(out, "                              execute the provided command for each group of\n");
623   fprintf(out, "                              identical files, with their names as arguments\n");
624   fprintf(out, "   -f <string>, --result-prefix <string>\n");
625   fprintf(out, "                              for each group of identical files, write one\n");
626   fprintf(out, "                              result file whose name is the given prefix string\n");
627   fprintf(out, "                              followed by the group number, and containing\n");
628   fprintf(out, "                              one filename per line\n");
629   fprintf(out, "\n");
630   fprintf(out, "Report bugs and comments to <francois@fleuret.org>.\n");
631 }
632
633 /**********************************************************************/
634
635 static struct option long_options[] = {
636   { "version", no_argument, 0, 'v' },
637   { "help", no_argument, 0, 'h' },
638   { "same-inodes-are-different", no_argument, 0, 'i' },
639   { "real-paths", no_argument, 0, 'r' },
640   { "hide-matchings", no_argument, 0, 'c' },
641   { "no-group-ids", no_argument, 0, 'g' },
642   { "time-sort", no_argument, 0, 't' },
643   { "ignore-dots", no_argument, 0, 'd' },
644   { "ignore-empty", no_argument, 0, '0' },
645   { "show-progress", no_argument, 0, 'p' },
646   { "exec", 1, 0, 'e' },
647   { "result-prefix", 1, 0, 'f' },
648   { 0, 0, 0, 0 }
649 };
650
651 int main(int argc, char **argv) {
652   int c;
653
654   setlocale (LC_ALL, "");
655
656   while ((c = getopt_long(argc, argv, "vhircgtd0pme:f:",
657                           long_options, NULL)) != -1) {
658     switch (c) {
659
660     case 'v':
661       printf("finddup version %s (%s)\n", VERSION_NUMBER, UNAME);
662       exit(EXIT_SUCCESS);
663       break;
664
665     case 'h':
666       usage(stdout);
667       exit(EXIT_SUCCESS);
668
669       break;
670
671     case 'd':
672       ignore_dotfiles = 1;
673       break;
674
675     case '0':
676       ignore_empty_files = 1;
677       break;
678
679     case 'r':
680       show_realpaths = 1;
681       break;
682
683     case 'i':
684       same_inodes_are_different = 1;
685       break;
686
687     case 'g':
688       show_groups = 0;
689       break;
690
691     case 't':
692       sort_by_time = 1;
693       break;
694
695     case 'p':
696       show_progress = 1;
697       break;
698
699     case 'c':
700       show_hits = 0;
701       break;
702
703     case 'e':
704       if(command_to_exec != 0) {
705         free(command_to_exec);
706       }
707       command_to_exec = strdup(optarg);
708       break;
709
710     case 'f':
711       if(result_file_prefix != 0) {
712         free(result_file_prefix);
713       }
714       result_file_prefix = strdup(optarg);
715       show_groups = 0;
716       break;
717
718     default:
719       usage(stderr);
720       exit(EXIT_FAILURE);
721     }
722   }
723
724   if(!isatty(STDERR_FILENO)) {
725     show_progress = 0;
726   }
727
728   if(optind + 2 == argc) {
729     start(argv[optind], argv[optind + 1]);
730   } else if(optind + 1 == argc) {
731     same_inodes_are_different = 1;
732     start(argv[optind], 0);
733   } else if(optind == argc) {
734     same_inodes_are_different = 1;
735     start(".", 0);
736   } else {
737     usage(stderr);
738     exit(EXIT_FAILURE);
739   }
740
741   exit(EXIT_SUCCESS);
742 }