Imported Upstream version 1.40
[platform/upstream/fdupes.git] / fdupes.c
1 /* FDUPES Copyright (c) 1999 Adrian Lopez
2
3    Permission is hereby granted, free of charge, to any person
4    obtaining a copy of this software and associated documentation files
5    (the "Software"), to deal in the Software without restriction,
6    including without limitation the rights to use, copy, modify, merge,
7    publish, distribute, sublicense, and/or sell copies of the Software,
8    and to permit persons to whom the Software is furnished to do so,
9    subject to the following conditions:
10
11    The above copyright notice and this permission notice shall be
12    included in all copies or substantial portions of the Software.
13
14    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
15    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
16    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
17    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
18    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
19    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
20    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
21
22 #include <stdio.h>
23 #include <stdarg.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <dirent.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <getopt.h>
30 #include <string.h>
31 #include <errno.h>
32
33 #ifndef EXTERNAL_MD5
34 #include "md5/md5.h"
35 #endif
36
37 #define ISFLAG(a,b) ((a & b) == b)
38 #define SETFLAG(a,b) (a |= b)
39
40 #define F_RECURSE           0x001
41 #define F_HIDEPROGRESS      0x002
42 #define F_DSAMELINE         0x004
43 #define F_FOLLOWLINKS       0x008
44 #define F_DELETEFILES       0x010
45 #define F_EXCLUDEEMPTY      0x020
46 #define F_CONSIDERHARDLINKS 0x040
47 #define F_SHOWSIZE          0x080
48 #define F_OMITFIRST         0x100
49
50 char *program_name;
51
52 unsigned long flags = 0;
53
54 #define CHUNK_SIZE 8192
55
56 #define INPUT_SIZE 256
57
58 typedef struct _file {
59   char *d_name;
60   off_t size;
61   char *crcsignature;
62   ino_t inode;
63   int hasdupes; /* true only if file is first on duplicate chain */
64   struct _file *duplicates;
65   struct _file *next;
66 } file_t;
67
68 typedef struct _filetree {
69   file_t *file; 
70 #ifdef EXPERIMENTAL_RBTREE
71   unsigned char color;
72   struct _filetree *parent;
73 #endif
74   struct _filetree *left;
75   struct _filetree *right;
76 } filetree_t;
77
78 #ifdef EXPERIMENTAL_RBTREE
79 #define COLOR_RED    0
80 #define COLOR_BLACK  1
81 #endif
82
83 void errormsg(char *message, ...)
84 {
85   va_list ap;
86
87   va_start(ap, message);
88
89   fprintf(stderr, "\r%40s\r%s: ", "", program_name);
90   vfprintf(stderr, message, ap);
91 }
92
93 void escapefilename(char *escape_list, char **filename_ptr)
94 {
95   int x;
96   int tx;
97   char *tmp;
98   char *filename;
99
100   filename = *filename_ptr;
101
102   tmp = (char*) malloc(strlen(filename) * 2 + 1);
103   if (tmp == NULL) {
104     errormsg("out of memory!\n");
105     exit(1);
106   }
107
108   for (x = 0, tx = 0; x < strlen(filename); x++) {
109     if (strchr(escape_list, filename[x]) != NULL) tmp[tx++] = '\\';
110     tmp[tx++] = filename[x];
111   }
112
113   tmp[tx] = '\0';
114
115   if (x != tx) {
116     *filename_ptr = realloc(*filename_ptr, strlen(tmp) + 1);
117     if (*filename_ptr == NULL) {
118       errormsg("out of memory!\n");
119       exit(1);
120     }
121     strcpy(*filename_ptr, tmp);
122   }
123 }
124
125 off_t filesize(char *filename) {
126   struct stat s;
127
128   if (stat(filename, &s) != 0) return -1;
129
130   return s.st_size;
131 }
132
133 ino_t getinode(char *filename) {
134   struct stat s;
135    
136   if (stat(filename, &s) != 0) return 0;
137
138   return s.st_ino;   
139 }
140
141 int grokdir(char *dir, file_t **filelistp)
142 {
143   DIR *cd;
144   file_t *newfile;
145   struct dirent *dirinfo;
146   int lastchar;
147   int filecount = 0;
148   struct stat info;
149   struct stat linfo;
150   static int progress = 0;
151   static char indicator[] = "-\\|/";
152
153   cd = opendir(dir);
154
155   if (!cd) {
156     errormsg("could not chdir to %s\n", dir);
157     return 0;
158   }
159
160   while ((dirinfo = readdir(cd)) != NULL) {
161     if (strcmp(dirinfo->d_name, ".") && strcmp(dirinfo->d_name, "..")) {
162       if (!ISFLAG(flags, F_HIDEPROGRESS)) {
163         fprintf(stderr, "\rBuilding file list %c ", indicator[progress]);
164         progress = (progress + 1) % 4;
165       }
166
167       newfile = (file_t*) malloc(sizeof(file_t));
168
169       if (!newfile) {
170         errormsg("out of memory!\n");
171         closedir(cd);
172         exit(1);
173       } else newfile->next = *filelistp;
174
175       newfile->inode = 0;
176       newfile->crcsignature = NULL;
177       newfile->duplicates = NULL;
178       newfile->hasdupes = 0;
179
180       newfile->d_name = (char*)malloc(strlen(dir)+strlen(dirinfo->d_name)+2);
181
182       if (!newfile->d_name) {
183         errormsg("out of memory!\n");
184         free(newfile);
185         closedir(cd);
186         exit(1);
187       }
188
189       strcpy(newfile->d_name, dir);
190       lastchar = strlen(dir) - 1;
191       if (lastchar >= 0 && dir[lastchar] != '/')
192         strcat(newfile->d_name, "/");
193       strcat(newfile->d_name, dirinfo->d_name);
194       
195       if (filesize(newfile->d_name) == 0 && ISFLAG(flags, F_EXCLUDEEMPTY)) {
196         free(newfile->d_name);
197         free(newfile);
198         continue;
199       }
200
201       if (stat(newfile->d_name, &info) == -1) {
202         free(newfile->d_name);
203         free(newfile);
204         continue;
205       }
206
207       if (lstat(newfile->d_name, &linfo) == -1) {
208         free(newfile->d_name);
209         free(newfile);
210         continue;
211       }
212
213       if (S_ISDIR(info.st_mode)) {
214         if (ISFLAG(flags, F_RECURSE) && (ISFLAG(flags, F_FOLLOWLINKS) || !S_ISLNK(linfo.st_mode)))
215           filecount += grokdir(newfile->d_name, filelistp);
216         free(newfile->d_name);
217         free(newfile);
218       } else {
219         if (S_ISREG(linfo.st_mode) || (S_ISLNK(linfo.st_mode) && ISFLAG(flags, F_FOLLOWLINKS))) {
220           *filelistp = newfile;
221           filecount++;
222         } else {
223           free(newfile->d_name);
224           free(newfile);
225         }
226       }
227     }
228   }
229
230   closedir(cd);
231
232   return filecount;
233 }
234
235 #ifndef EXTERNAL_MD5
236
237 /* If EXTERNAL_MD5 is not defined, use L. Peter Deutsch's MD5 library. 
238  */
239 char *getcrcsignature(char *filename)
240 {
241   int x;
242   off_t fsize;
243   off_t toread;
244   md5_state_t state;
245   md5_byte_t digest[16];  
246   static md5_byte_t chunk[CHUNK_SIZE];
247   static char signature[16*2 + 1]; 
248   char *sigp;
249   FILE *file;
250    
251   md5_init(&state);
252
253   fsize = filesize(filename);
254
255   file = fopen(filename, "rb");
256   if (file == NULL) {
257     errormsg("error opening file %s\n", filename);
258     return NULL;
259   }
260  
261   while (fsize > 0) {
262     toread = (fsize % CHUNK_SIZE) ? (fsize % CHUNK_SIZE) : CHUNK_SIZE;
263     if (fread(chunk, toread, 1, file) != 1) {
264       errormsg("error reading from file %s\n", filename);
265       return NULL;
266     }
267     md5_append(&state, chunk, toread);
268     fsize -= toread;
269   }
270
271   md5_finish(&state, digest);
272
273   sigp = signature;
274
275   for (x = 0; x < 16; x++) {
276     sprintf(sigp, "%02x", digest[x]);
277     sigp = strchr(sigp, '\0');
278   }
279
280   fclose(file);
281
282   return signature;
283 }
284
285 #endif /* [#ifndef EXTERNAL_MD5] */
286
287 #ifdef EXTERNAL_MD5
288
289 /* If EXTERNAL_MD5 is defined, use md5sum program to calculate signatures.
290  */
291 char *getcrcsignature(char *filename)
292 {
293   static char signature[256];
294   char *command;
295   char *separator;
296   FILE *result;
297
298   command = (char*) malloc(strlen(filename)+strlen(EXTERNAL_MD5)+2);
299   if (command == NULL) {
300     errormsg("out of memory\n");
301     exit(1);
302   }
303
304   sprintf(command, "%s %s", EXTERNAL_MD5, filename);
305
306   result = popen(command, "r");
307   if (result == NULL) {
308     errormsg("error invoking %s\n", EXTERNAL_MD5);
309     exit(1);
310   }
311  
312   free(command);
313
314   if (fgets(signature, 256, result) == NULL) {
315     errormsg("error generating signature for %s\n", filename);
316     return NULL;
317   }    
318   separator = strchr(signature, ' ');
319   if (separator) *separator = '\0';
320
321   pclose(result);
322
323   return signature;
324 }
325
326 #endif /* [#ifdef EXTERNAL_MD5] */
327
328 void purgetree(filetree_t *checktree)
329 {
330   if (checktree->left != NULL) purgetree(checktree->left);
331     
332   if (checktree->right != NULL) purgetree(checktree->right);
333     
334   free(checktree);
335 }
336
337 #ifdef EXPERIMENTAL_RBTREE
338 /* Use a red-black tree structure to store file information.
339  */
340
341 void rotate_left(filetree_t **root, filetree_t *node)
342 {
343   filetree_t *subject;
344
345   subject = node->right;
346   node->right = subject->left;
347
348   if (subject->left != NULL) subject->left->parent = node;
349   subject->parent = node->parent;
350
351   if (node->parent == NULL) {
352     *root = subject;
353   } else {
354     if (node == node->parent->left)
355       node->parent->left = subject;
356     else 
357       node->parent->right = subject;
358   }
359
360   subject->left = node;
361   node->parent = subject;
362 }
363
364 void rotate_right(filetree_t **root, filetree_t *node)
365 {
366   filetree_t *subject;
367
368   subject = node->left;
369   node->left = subject->right;
370
371   if (subject->right != NULL) subject->right->parent = node;
372   subject->parent = node->parent;
373
374   if (node->parent == NULL) {
375     *root = subject;
376   } else {
377     if (node == node->parent->left)
378       node->parent->left = subject;
379     else 
380       node->parent->right = subject;
381   }
382
383   subject->right = node;
384   node->parent = subject;
385 }
386
387 #define TREE_LEFT -1
388 #define TREE_RIGHT 1
389 #define TREE_ROOT  0
390
391 void registerfile(filetree_t **root, filetree_t *parent, int loc, file_t *file)
392 {
393   filetree_t *node;
394   filetree_t *uncle;
395
396   file->size = filesize(file->d_name);
397   file->inode = getinode(file->d_name);
398
399   node = (filetree_t*) malloc(sizeof(filetree_t));
400   if (node == NULL) {
401     errormsg("out of memory!\n");
402     exit(1);
403   }
404   
405   node->file = file;
406   node->left = NULL;
407   node->right = NULL;
408   node->parent = parent;
409   node->color = COLOR_RED;
410
411   if (loc == TREE_ROOT)
412     *root = node;
413   else if (loc == TREE_LEFT) 
414     parent->left = node;
415   else 
416     parent->right = node;
417
418   while (node != *root && node->parent->color == COLOR_RED) {
419     if (node->parent->parent == NULL) return;
420
421     if (node->parent == node->parent->parent->left) {
422       uncle = node->parent->parent->right;
423       if (uncle == NULL) return;
424
425       if (uncle->color == COLOR_RED) {
426         node->parent->color = COLOR_BLACK;
427         uncle->color = COLOR_BLACK;
428         node->parent->parent->color = COLOR_RED;
429         node = node->parent->parent;
430       } else {
431         if (node == node->parent->right) {
432           node = node->parent;
433           rotate_left(root, node);
434         }
435         node->parent->color = COLOR_BLACK;
436         node->parent->parent->color = COLOR_RED;
437         rotate_right(root, node->parent->parent);
438       }
439     } else {
440       uncle = node->parent->parent->left;
441       if (uncle == NULL) return;
442
443       if (uncle->color == COLOR_RED) {
444         node->parent->color = COLOR_BLACK;
445         uncle->color = COLOR_BLACK;
446         node->parent->parent->color = COLOR_RED;
447         node = node->parent->parent;
448       } else {
449         if (node == node->parent->right) {
450           node = node->parent;
451           rotate_left(root, node);
452         }
453         node->parent->color = COLOR_BLACK;
454         node->parent->parent->color = COLOR_RED;
455         rotate_right(root, node->parent->parent);
456       }
457     }
458   }
459
460   (*root)->color = COLOR_BLACK;
461 }
462
463 #endif /* [#ifdef EXPERIMENTAL_RBTREE] */
464
465 #ifndef EXPERIMENTAL_RBTREE
466
467 int registerfile(filetree_t **branch, file_t *file)
468 {
469   file->size = filesize(file->d_name);
470   file->inode = getinode(file->d_name);
471
472   *branch = (filetree_t*) malloc(sizeof(filetree_t));
473   if (*branch == NULL) {
474     errormsg("out of memory!\n");
475     exit(1);
476   }
477   
478   (*branch)->file = file;
479   (*branch)->left = NULL;
480   (*branch)->right = NULL;
481
482   return 1;
483 }
484
485 #endif /* [#ifndef EXPERIMENTAL_RBTREE] */
486
487 file_t *checkmatch(filetree_t **root, filetree_t *checktree, file_t *file)
488 {
489   int cmpresult;
490   char *crcsignature;
491   off_t fsize;
492
493   /* If inodes are equal one of the files is a hard link, which
494      is usually not accidental. We don't want to flag them as 
495      duplicates, unless the user specifies otherwise. */
496
497   if (!ISFLAG(flags, F_CONSIDERHARDLINKS) && getinode(file->d_name) == 
498    checktree->file->inode) return NULL;
499
500   fsize = filesize(file->d_name);
501   
502   if (fsize < checktree->file->size) 
503     cmpresult = -1;
504   else 
505     if (fsize > checktree->file->size) cmpresult = 1;
506   else {
507     if (checktree->file->crcsignature == NULL) {
508       crcsignature = getcrcsignature(checktree->file->d_name);
509       if (crcsignature == NULL) return NULL;
510
511       checktree->file->crcsignature = (char*) malloc(strlen(crcsignature)+1);
512       if (checktree->file->crcsignature == NULL) {
513         errormsg("out of memory\n");
514         exit(1);
515       }
516       strcpy(checktree->file->crcsignature, crcsignature);
517     }
518
519     if (file->crcsignature == NULL) {
520       crcsignature = getcrcsignature(file->d_name);
521       if (crcsignature == NULL) return NULL;
522
523       file->crcsignature = (char*) malloc(strlen(crcsignature)+1);
524       if (file->crcsignature == NULL) {
525         errormsg("out of memory\n");
526         exit(1);
527       }
528       strcpy(file->crcsignature, crcsignature);
529     }
530
531     cmpresult = strcmp(file->crcsignature, checktree->file->crcsignature);
532   }
533
534   if (cmpresult < 0) {
535     if (checktree->left != NULL) {
536       return checkmatch(root, checktree->left, file);
537     } else {
538 #ifndef EXPERIMENTAL_RBTREE
539       registerfile(&(checktree->left), file);
540 #else
541       registerfile(root, checktree, TREE_LEFT, file);
542 #endif
543       return NULL;
544     }
545   } else if (cmpresult > 0) {
546     if (checktree->right != NULL) {
547       return checkmatch(root, checktree->right, file);
548     } else {
549 #ifndef EXPERIMENTAL_RBTREE
550       registerfile(&(checktree->right), file);
551 #else
552       registerfile(root, checktree, TREE_RIGHT, file);
553 #endif
554       return NULL;
555     }
556   } else return checktree->file;
557 }
558
559 /* Do a bit-for-bit comparison in case two different files produce the 
560    same signature. Unlikely, but better safe than sorry. */
561
562 int confirmmatch(FILE *file1, FILE *file2)
563 {
564   unsigned char c1;
565   unsigned char c2;
566   size_t r1;
567   size_t r2;
568   
569   fseek(file1, 0, SEEK_SET);
570   fseek(file2, 0, SEEK_SET);
571
572   do {
573     r1 = fread(&c1, sizeof(c1), 1, file1);
574     r2 = fread(&c2, sizeof(c2), 1, file2);
575
576     if (c1 != c2) return 0; /* file contents are different */
577   } while (r1 && r2);
578   
579   if (r1 != r2) return 0; /* file lengths are different */
580
581   return 1;
582 }
583
584 void printmatches(file_t *files)
585 {
586   file_t *tmpfile;
587
588   while (files != NULL) {
589     if (files->hasdupes) {
590       if (!ISFLAG(flags, F_OMITFIRST)) {
591         if (ISFLAG(flags, F_SHOWSIZE)) printf("%ld byte%seach:\n", files->size,
592          (files->size != 1) ? "s " : " ");
593         if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &files->d_name);
594         printf("%s%c", files->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
595       }
596       tmpfile = files->duplicates;
597       while (tmpfile != NULL) {
598         if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &tmpfile->d_name);
599         printf("%s%c", tmpfile->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
600         tmpfile = tmpfile->duplicates;
601       }
602       printf("\n");
603
604     }
605       
606     files = files->next;
607   }
608 }
609
610 void autodelete(file_t *files)
611 {
612   int counter;
613   int groups = 0;
614   int curgroup = 0;
615   file_t *tmpfile;
616   file_t *curfile;
617   file_t **dupelist;
618   int *preserve;
619   char *preservestr;
620   char *token;
621   char *tstr;
622   int number;
623   int sum;
624   int max = 0;
625   int x;
626   int i;
627
628   curfile = files;
629   
630   while (curfile) {
631     if (curfile->hasdupes) {
632       counter = 1;
633       groups++;
634
635       tmpfile = curfile->duplicates;
636       while (tmpfile) {
637         counter++;
638         tmpfile = tmpfile->duplicates;
639       }
640       
641       if (counter > max) max = counter;
642     }
643     
644     curfile = curfile->next;
645   }
646
647   max++;
648
649   dupelist = (file_t**) malloc(sizeof(file_t*) * max);
650   preserve = (int*) malloc(sizeof(int) * max);
651   preservestr = (char*) malloc(INPUT_SIZE);
652
653   if (!dupelist || !preserve || !preservestr) {
654     errormsg("out of memory\n");
655     exit(1);
656   }
657
658   while (files) {
659     if (files->hasdupes) {
660       curgroup++;
661       counter = 1;
662       dupelist[counter] = files;
663
664       printf("[%d] %s\n", counter, files->d_name);
665
666       tmpfile = files->duplicates;
667
668       while (tmpfile) {
669         dupelist[++counter] = tmpfile;
670         printf("[%d] %s\n", counter, tmpfile->d_name);
671         tmpfile = tmpfile->duplicates;
672       }
673
674       printf("\n");
675
676       do {
677         printf("Set %d of %d, preserve files [1 - %d, all]", 
678           curgroup, groups, counter);
679         if (ISFLAG(flags, F_SHOWSIZE)) printf(" (%ld byte%seach)", files->size,
680           (files->size != 1) ? "s " : " ");
681         printf(": ");
682         fflush(stdout);
683
684         fgets(preservestr, INPUT_SIZE, stdin);
685
686         i = strlen(preservestr) - 1;
687
688         while (preservestr[i]!='\n'){ /* tail of buffer must be a newline */
689           tstr = (char*)
690             realloc(preservestr, strlen(preservestr) + 1 + INPUT_SIZE);
691           if (!tstr) { /* couldn't allocate memory, treat as fatal */
692             errormsg("out of memory!\n");
693             exit(1);
694           }
695
696           preservestr = tstr;
697           if (!fgets(preservestr + i + 1, INPUT_SIZE, stdin))
698             break; /* stop if fgets fails -- possible EOF? */
699           i = strlen(preservestr)-1;
700         }
701
702         for (x = 1; x <= counter; x++) preserve[x] = 0;
703         
704         token = strtok(preservestr, " ,\n");
705         
706         while (token != NULL) {
707           if (strcasecmp(token, "all") == 0)
708             for (x = 0; x <= counter; x++) preserve[x] = 1;
709           
710           number = 0;
711           sscanf(token, "%d", &number);
712           if (number > 0 && number <= counter) preserve[number] = 1;
713           
714           token = strtok(NULL, " ,\n");
715         }
716       
717         for (sum = 0, x = 1; x <= counter; x++) sum += preserve[x];
718       } while (sum < 1); /* make sure we've preserved at least one file */
719
720       printf("\n");
721
722       for (x = 1; x <= counter; x++) { 
723         if (preserve[x])
724           printf("   [+] %s\n", dupelist[x]->d_name);
725         else {
726           printf("   [-] %s\n", dupelist[x]->d_name);
727           remove(dupelist[x]->d_name);
728         }
729       }
730       printf("\n");
731     }
732     
733     files = files->next;
734   }
735
736   free(dupelist);
737   free(preserve);
738   free(preservestr);
739 }
740
741 void help_text()
742 {
743   printf("Usage: fdupes [options] DIRECTORY...\n\n");
744
745   printf(" -r --recurse     \tinclude files residing in subdirectories\n");
746   printf(" -s --symlinks    \tfollow symlinks\n");
747   printf(" -H --hardlinks   \tnormally, when two or more files point to the same\n");
748   printf("                  \tdisk area they are treated as non-duplicates; this\n"); 
749   printf("                  \toption will change this behavior\n");
750   printf(" -n --noempty     \texclude zero-length files from consideration\n");
751   printf(" -f --omitfirst   \tomit the first file in each set of matches\n");
752   printf(" -1 --sameline    \tlist each set of matches on a single line\n");
753   printf(" -S --size        \tshow size of duplicate files\n");
754   printf(" -q --quiet       \thide progress indicator\n");
755   printf(" -d --delete      \tprompt user for files to preserve and delete all\n"); 
756   printf("                  \tothers; important: under particular circumstances,\n");
757   printf("                  \tdata may be lost when using this option together\n");
758   printf("                  \twith -s or --symlinks, or when specifying a\n");
759   printf("                  \tparticular directory more than once; refer to the\n");
760   printf("                  \tfdupes documentation for additional information\n");
761   printf(" -v --version     \tdisplay fdupes version\n");
762   printf(" -h --help        \tdisplay this help message\n\n");
763 }
764
765 int main(int argc, char **argv) {
766   int x;
767   int opt;
768   FILE *file1;
769   FILE *file2;
770   file_t *files = NULL;
771   file_t *curfile;
772   file_t *match = NULL;
773   filetree_t *checktree = NULL;
774   int filecount = 0;
775   int progress = 0;
776  
777   static struct option long_options[] = 
778   {
779     { "omitfirst", 0, 0, 'f' },
780     { "recurse", 0, 0, 'r' },
781     { "quiet", 0, 0, 'q' },
782     { "sameline", 0, 0, '1' },
783     { "size", 0, 0, 'S' },
784     { "symlinks", 0, 0, 's' },
785     { "hardlinks", 0, 0, 'H' },
786     { "noempty", 0, 0, 'n' },
787     { "delete", 0, 0, 'd' },
788     { "version", 0, 0, 'v' },
789     { "help", 0, 0, 'h' },
790     { 0, 0, 0, 0 }
791   };
792
793   program_name = argv[0];
794
795   while ((opt = getopt_long(argc, argv, "frq1SsHndvh", long_options, NULL)) != EOF) {
796     switch (opt) {
797     case 'f':
798       SETFLAG(flags, F_OMITFIRST);
799       break;
800     case 'r':
801       SETFLAG(flags, F_RECURSE);
802       break;
803     case 'q':
804       SETFLAG(flags, F_HIDEPROGRESS);
805       break;
806     case '1':
807       SETFLAG(flags, F_DSAMELINE);
808       break;
809     case 'S':
810       SETFLAG(flags, F_SHOWSIZE);
811       break;
812     case 's':
813       SETFLAG(flags, F_FOLLOWLINKS);
814       break;
815     case 'H':
816       SETFLAG(flags, F_CONSIDERHARDLINKS);
817       break;
818     case 'n':
819       SETFLAG(flags, F_EXCLUDEEMPTY);
820       break;
821     case 'd':
822       SETFLAG(flags, F_DELETEFILES);
823       break;
824     case 'v':
825       printf("fdupes %s\n", VERSION);
826       exit(0);
827     case 'h':
828       help_text();
829       exit(1);
830     default:
831       fprintf(stderr, "Try `fdupes --help' for more information\n");
832       exit(1);
833     }
834   }
835
836   if (optind >= argc) {
837     errormsg("no directories specified\n");
838     exit(1);
839   }
840
841   for (x = optind; x < argc; x++) filecount += grokdir(argv[x], &files);
842
843   if (!files) exit(0);
844   
845   curfile = files;
846
847   while (curfile) {
848     if (!checktree) 
849 #ifndef EXPERIMENTAL_RBTREE
850       registerfile(&checktree, curfile);
851 #else
852       registerfile(&checktree, NULL, TREE_ROOT, curfile);
853 #endif
854     else 
855       match = checkmatch(&checktree, checktree, curfile);
856
857     if (match != NULL) {
858       file1 = fopen(curfile->d_name, "rb");
859       if (!file1) {
860         curfile = curfile->next;
861         continue;
862       }
863
864       file2 = fopen(match->d_name, "rb");
865       if (!file2) {
866         fclose(file1);
867         curfile = curfile->next;
868         continue;
869       }
870  
871       if (confirmmatch(file1, file2)) {
872         match->hasdupes = 1;
873         curfile->duplicates = match->duplicates;
874         match->duplicates = curfile;
875       }
876       
877       fclose(file1);
878       fclose(file2);
879     }
880
881     curfile = curfile->next;
882
883     if (!ISFLAG(flags, F_HIDEPROGRESS)) {
884       fprintf(stderr, "\rProgress [%d/%d] %d%% ", progress, filecount,
885        (int)((float) progress / (float) filecount * 100.0));
886       progress++;
887     }
888   }
889
890   if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
891
892   if (ISFLAG(flags, F_DELETEFILES)) autodelete(files); 
893   else printmatches(files);
894
895   while (files) {
896     curfile = files->next;
897     free(files->d_name);
898     free(files);
899     files = curfile;
900   }
901   
902   purgetree(checktree);
903
904   return 0;
905 }