Imported Upstream version 2.2.1
[platform/upstream/fdupes.git] / fdupes.c
1 /* FDUPES Copyright (c) 1999-2022 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 "config.h"
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <strings.h>
27 #include <sys/stat.h>
28 #include <dirent.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #ifdef HAVE_GETOPT_H
33 #include <getopt.h>
34 #endif
35 #include <errno.h>
36 #include <libgen.h>
37 #include <locale.h>
38 #ifndef NO_NCURSES
39 #ifdef HAVE_NCURSESW_CURSES_H
40   #include <ncursesw/curses.h>
41 #else
42   #include <curses.h>
43 #endif
44 #include "ncurses-interface.h"
45 #endif
46 #include "fdupes.h"
47 #include "confirmmatch.h"
48 #include "errormsg.h"
49 #include "log.h"
50 #include "sigint.h"
51 #include "flags.h"
52 #include "removeifnotchanged.h"
53
54 long long minsize = -1;
55 long long maxsize = -1;
56
57 typedef enum {
58   ORDER_MTIME = 0,
59   ORDER_CTIME,
60   ORDER_NAME
61 } ordertype_t;
62
63 char *program_name;
64
65 ordertype_t ordertype = ORDER_MTIME;
66
67 #define MD5_DIGEST_LENGTH 16
68
69 typedef struct _filetree {
70   file_t *file; 
71   struct _filetree *left;
72   struct _filetree *right;
73 } filetree_t;
74
75 void escapefilename(char *escape_list, char **filename_ptr)
76 {
77   int x;
78   int tx;
79   char *tmp;
80   char *filename;
81
82   filename = *filename_ptr;
83
84   tmp = (char*) malloc(strlen(filename) * 2 + 1);
85   if (tmp == NULL) {
86     errormsg("out of memory!\n");
87     exit(1);
88   }
89
90   for (x = 0, tx = 0; x < strlen(filename); x++) {
91     if (strchr(escape_list, filename[x]) != NULL) tmp[tx++] = '\\';
92     tmp[tx++] = filename[x];
93   }
94
95   tmp[tx] = '\0';
96
97   if (x != tx) {
98     *filename_ptr = realloc(*filename_ptr, strlen(tmp) + 1);
99     if (*filename_ptr == NULL) {
100       errormsg("out of memory!\n");
101       exit(1);
102     }
103     strcpy(*filename_ptr, tmp);
104   }
105 }
106
107 dev_t getdevice(char *filename) {
108   struct stat s;
109
110   if (stat(filename, &s) != 0) return 0;
111
112   return s.st_dev;
113 }
114
115 ino_t getinode(char *filename) {
116   struct stat s;
117    
118   if (stat(filename, &s) != 0) return 0;
119
120   return s.st_ino;   
121 }
122
123 char *fmttime(time_t t) {
124   static char buf[64];
125
126   strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", localtime(&t));
127
128   return buf;
129 }
130
131 char **cloneargs(int argc, char **argv)
132 {
133   int x;
134   char **args;
135
136   args = (char **) malloc(sizeof(char*) * argc);
137   if (args == NULL) {
138     errormsg("out of memory!\n");
139     exit(1);
140   }
141
142   for (x = 0; x < argc; x++) {
143     args[x] = (char*) malloc(strlen(argv[x]) + 1);
144     if (args[x] == NULL) {
145       free(args);
146       errormsg("out of memory!\n");
147       exit(1);
148     }
149
150     strcpy(args[x], argv[x]);
151   }
152
153   return args;
154 }
155
156 int findarg(char *arg, int start, int argc, char **argv)
157 {
158   int x;
159   
160   for (x = start; x < argc; x++)
161     if (strcmp(argv[x], arg) == 0) 
162       return x;
163
164   return x;
165 }
166
167 /* Find the first non-option argument after specified option. */
168 int nonoptafter(char *option, int argc, char **oldargv, 
169                       char **newargv, int optind) 
170 {
171   int x;
172   int targetind;
173   int testind;
174   int startat = 1;
175
176   targetind = findarg(option, 1, argc, oldargv);
177     
178   for (x = optind; x < argc; x++) {
179     testind = findarg(newargv[x], startat, argc, oldargv);
180     if (testind > targetind) return x;
181     else startat = testind;
182   }
183
184   return x;
185 }
186
187 void getfilestats(file_t *file, struct stat *info, struct stat *linfo)
188 {
189   file->size = info->st_size;;
190   file->inode = info->st_ino;
191   file->device = info->st_dev;
192   file->ctime = info->st_ctime;
193   file->mtime = info->st_mtime;
194 }
195
196 int grokdir(char *dir, file_t **filelistp, struct stat *logfile_status)
197 {
198   DIR *cd;
199   file_t *newfile;
200   struct dirent *dirinfo;
201   int lastchar;
202   int filecount = 0;
203   struct stat info;
204   struct stat linfo;
205   static int progress = 0;
206   static char indicator[] = "-\\|/";
207   char *fullname, *name;
208
209   cd = opendir(dir);
210
211   if (!cd) {
212     errormsg("could not chdir to %s\n", dir);
213     return 0;
214   }
215
216   while ((dirinfo = readdir(cd)) != NULL) {
217     if (strcmp(dirinfo->d_name, ".") && strcmp(dirinfo->d_name, "..")) {
218       if (!ISFLAG(flags, F_HIDEPROGRESS)) {
219         fprintf(stderr, "\rBuilding file list %c ", indicator[progress]);
220         progress = (progress + 1) % 4;
221       }
222
223       newfile = (file_t*) malloc(sizeof(file_t));
224
225       if (!newfile) {
226         errormsg("out of memory!\n");
227         closedir(cd);
228         exit(1);
229       } else newfile->next = *filelistp;
230
231       newfile->device = 0;
232       newfile->inode = 0;
233       newfile->crcsignature = NULL;
234       newfile->crcpartial = NULL;
235       newfile->duplicates = NULL;
236       newfile->hasdupes = 0;
237
238       newfile->d_name = (char*)malloc(strlen(dir)+strlen(dirinfo->d_name)+2);
239
240       if (!newfile->d_name) {
241         errormsg("out of memory!\n");
242         free(newfile);
243         closedir(cd);
244         exit(1);
245       }
246
247       strcpy(newfile->d_name, dir);
248       lastchar = strlen(dir) - 1;
249       if (lastchar >= 0 && dir[lastchar] != '/')
250         strcat(newfile->d_name, "/");
251       strcat(newfile->d_name, dirinfo->d_name);
252       
253       if (ISFLAG(flags, F_EXCLUDEHIDDEN)) {
254         fullname = strdup(newfile->d_name);
255         if (fullname == 0)
256         {
257           errormsg("out of memory!\n");
258           free(newfile);
259           closedir(cd);
260           exit(1);
261         }
262         name = basename(fullname);
263         if (name[0] == '.' && strcmp(name, ".") && strcmp(name, "..") ) {
264           free(newfile->d_name);
265           free(newfile);
266           free(fullname);
267           continue;
268         }
269         free(fullname);
270       }
271
272       if (stat(newfile->d_name, &info) == -1) {
273         free(newfile->d_name);
274         free(newfile);
275         continue;
276       }
277       
278       if (!S_ISDIR(info.st_mode) && (((info.st_size == 0 && ISFLAG(flags, F_EXCLUDEEMPTY)) || info.st_size < minsize || (info.st_size > maxsize && maxsize != -1)))) {
279         free(newfile->d_name);
280         free(newfile);
281         continue;
282       }
283
284       /* ignore logfile */
285       if (logfile_status != 0 && info.st_dev == logfile_status->st_dev && info.st_ino == logfile_status->st_ino)
286       {
287         free(newfile->d_name);
288         free(newfile);
289         continue;
290       }
291
292       if (lstat(newfile->d_name, &linfo) == -1) {
293         free(newfile->d_name);
294         free(newfile);
295         continue;
296       }
297
298       if (S_ISDIR(info.st_mode)) {
299         if (ISFLAG(flags, F_RECURSE) && (ISFLAG(flags, F_FOLLOWLINKS) || !S_ISLNK(linfo.st_mode)))
300           filecount += grokdir(newfile->d_name, filelistp, logfile_status);
301         free(newfile->d_name);
302         free(newfile);
303       } else {
304         if (S_ISREG(linfo.st_mode) || (S_ISLNK(linfo.st_mode) && ISFLAG(flags, F_FOLLOWLINKS))) {
305           getfilestats(newfile, &info, &linfo);
306           *filelistp = newfile;
307           filecount++;
308         } else {
309           free(newfile->d_name);
310           free(newfile);
311         }
312       }
313     }
314   }
315
316   closedir(cd);
317
318   return filecount;
319 }
320
321 md5_byte_t *getcrcsignatureuntil(char *filename, off_t fsize, off_t max_read)
322 {
323   off_t toread;
324   md5_state_t state;
325   static md5_byte_t digest[MD5_DIGEST_LENGTH];  
326   static md5_byte_t chunk[CHUNK_SIZE];
327   FILE *file;
328    
329   md5_init(&state);
330   
331   if (max_read != 0 && fsize > max_read)
332     fsize = max_read;
333
334   file = fopen(filename, "rb");
335   if (file == NULL) {
336     errormsg("error opening file %s\n", filename);
337     return NULL;
338   }
339  
340   while (fsize > 0) {
341     toread = (fsize >= CHUNK_SIZE) ? CHUNK_SIZE : fsize;
342     if (fread(chunk, toread, 1, file) != 1) {
343       errormsg("error reading from file %s\n", filename);
344       fclose(file);
345       return NULL;
346     }
347     md5_append(&state, chunk, toread);
348     fsize -= toread;
349   }
350
351   md5_finish(&state, digest);
352
353   fclose(file);
354
355   return digest;
356 }
357
358 md5_byte_t *getcrcsignature(char *filename, off_t fsize)
359 {
360   return getcrcsignatureuntil(filename, fsize, 0);
361 }
362
363 md5_byte_t *getcrcpartialsignature(char *filename, off_t fsize)
364 {
365   return getcrcsignatureuntil(filename, fsize, PARTIAL_MD5_SIZE);
366 }
367
368 int md5cmp(const md5_byte_t *a, const md5_byte_t *b)
369 {
370   int x;
371
372   for (x = 0; x < MD5_DIGEST_LENGTH; ++x)
373   {
374     if (a[x] < b[x])
375       return -1;
376     else if (a[x] > b[x])
377       return 1;
378   }
379
380   return 0;
381 }
382
383 void md5copy(md5_byte_t *to, const md5_byte_t *from)
384 {
385   int x;
386
387   for (x = 0; x < MD5_DIGEST_LENGTH; ++x)
388     to[x] = from[x];
389 }
390
391 void purgetree(filetree_t *checktree)
392 {
393   if (checktree->left != NULL) purgetree(checktree->left);
394     
395   if (checktree->right != NULL) purgetree(checktree->right);
396     
397   free(checktree);
398 }
399
400 int registerfile(filetree_t **branch, file_t *file)
401 {
402   *branch = (filetree_t*) malloc(sizeof(filetree_t));
403   if (*branch == NULL) {
404     errormsg("out of memory!\n");
405     exit(1);
406   }
407   
408   (*branch)->file = file;
409   (*branch)->left = NULL;
410   (*branch)->right = NULL;
411
412   return 1;
413 }
414
415 int same_permissions(char* name1, char* name2)
416 {
417     struct stat s1, s2;
418
419     if (stat(name1, &s1) != 0) return -1;
420     if (stat(name2, &s2) != 0) return -1;
421
422     return (s1.st_mode == s2.st_mode &&
423             s1.st_uid == s2.st_uid &&
424             s1.st_gid == s2.st_gid);
425 }
426
427 int is_hardlink(filetree_t *checktree, file_t *file)
428 {
429   file_t *dupe;
430
431   if ((file->inode == checktree->file->inode) &&
432       (file->device == checktree->file->device))
433         return 1;
434
435   if (checktree->file->hasdupes)
436   {
437     dupe = checktree->file->duplicates;
438
439     do {
440       if ((file->inode == dupe->inode) &&
441           (file->device == dupe->device))
442             return 1;
443
444       dupe = dupe->duplicates;
445     } while (dupe != NULL);
446   }
447
448   return 0;
449 }
450
451 /* check whether two paths represent the same file (deleting one would delete the other) */
452 int is_same_file(file_t *file_a, file_t *file_b)
453 {
454   char *filename_a;
455   char *filename_b;
456   char *dirname_a;
457   char *dirname_b;
458   char *basename_a;
459   char *basename_b;
460   struct stat dirstat_a;
461   struct stat dirstat_b;
462
463   /* if files on different devices and/or different inodes, they are not the same file */
464   if (file_a->device != file_b->device || file_a->inode != file_b->inode)
465     return 0;
466
467   /* copy filenames (basename and dirname may modify these) */
468   filename_a = strdup(file_a->d_name);
469   if (filename_a == 0)
470     return -1;
471
472   filename_b = strdup(file_b->d_name);
473   if (filename_b == 0)
474     return -1;
475
476   /* get file basenames */
477   basename_a = basename(filename_a);
478   memmove(filename_a, basename_a, strlen(basename_a) + 1);
479
480   basename_b = basename(filename_b);
481   memmove(filename_b, basename_b, strlen(basename_b) + 1);
482
483   /* if files have different names, they are not the same file */
484   if (strcmp(filename_a, filename_b) != 0)
485   {
486     free(filename_b);
487     free(filename_a);
488     return 0;
489   }
490
491   /* restore paths */
492   strcpy(filename_a, file_a->d_name);
493   strcpy(filename_b, file_b->d_name);
494
495   /* get directory names */
496   dirname_a = dirname(filename_a);
497   if (stat(dirname_a, &dirstat_a) != 0)
498   {
499     free(filename_b);
500     free(filename_a);
501     return -1;
502   }
503
504   dirname_b = dirname(filename_b);
505   if (stat(dirname_b, &dirstat_b) != 0)
506   {
507     free(filename_b);
508     free(filename_a);
509     return -1;
510   }
511
512   free(filename_b);
513   free(filename_a);
514
515   /* if directories on which files reside are different, they are not the same file */
516   if (dirstat_a.st_dev != dirstat_b.st_dev || dirstat_a.st_ino != dirstat_b.st_ino)
517     return 0;
518
519   /* same device, inode, filename, and directory; therefore, same file */
520   return 1;
521 }
522
523 /* check whether given tree node already contains a copy of given file */
524 int has_same_file(filetree_t *checktree, file_t *file)
525 {
526   file_t *dupe;
527
528   if (is_same_file(checktree->file, file))
529     return 1;
530
531   if (checktree->file->hasdupes)
532   {
533     dupe = checktree->file->duplicates;
534
535     do {
536       if (is_same_file(dupe, file))
537         return 1;
538
539       dupe = dupe->duplicates;
540     } while (dupe != NULL);
541   }
542
543   return 0;
544 }
545
546 file_t **checkmatch(filetree_t **root, filetree_t *checktree, file_t *file)
547 {
548   int cmpresult;
549   md5_byte_t *crcsignature;
550
551   if (ISFLAG(flags, F_CONSIDERHARDLINKS))
552   {
553     /* If node already contains file, we don't want to add it again.
554     */
555     if (has_same_file(checktree, file))
556       return NULL;
557   }
558   else
559   {
560     /* If device and inode fields are equal one of the files is a
561        hard link to the other or the files have been listed twice
562        unintentionally. We don't want to flag these files as
563        duplicates unless the user specifies otherwise.
564     */
565     if (is_hardlink(checktree, file))
566       return NULL;
567   }
568   
569   if (file->size < checktree->file->size)
570     cmpresult = -1;
571   else 
572     if (file->size > checktree->file->size) cmpresult = 1;
573   else
574     if (ISFLAG(flags, F_PERMISSIONS) &&
575         !same_permissions(file->d_name, checktree->file->d_name))
576         cmpresult = -1;
577   else {
578     if (checktree->file->crcpartial == NULL) {
579       crcsignature = getcrcpartialsignature(checktree->file->d_name, checktree->file->size);
580       if (crcsignature == NULL) {
581         errormsg ("cannot read file %s\n", checktree->file->d_name);
582         return NULL;
583       }
584
585       checktree->file->crcpartial = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
586       if (checktree->file->crcpartial == NULL) {
587         errormsg("out of memory\n");
588         exit(1);
589       }
590       md5copy(checktree->file->crcpartial, crcsignature);
591     }
592
593     if (file->crcpartial == NULL) {
594       crcsignature = getcrcpartialsignature(file->d_name, file->size);
595       if (crcsignature == NULL) {
596         errormsg ("cannot read file %s\n", file->d_name);
597         return NULL;
598       }
599
600       file->crcpartial = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
601       if (file->crcpartial == NULL) {
602         errormsg("out of memory\n");
603         exit(1);
604       }
605       md5copy(file->crcpartial, crcsignature);
606     }
607
608     cmpresult = md5cmp(file->crcpartial, checktree->file->crcpartial);
609
610     if (cmpresult == 0) {
611       if (checktree->file->crcsignature == NULL) {
612         crcsignature = getcrcsignature(checktree->file->d_name, checktree->file->size);
613         if (crcsignature == NULL) return NULL;
614
615         checktree->file->crcsignature = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
616         if (checktree->file->crcsignature == NULL) {
617           errormsg("out of memory\n");
618           exit(1);
619         }
620         md5copy(checktree->file->crcsignature, crcsignature);
621       }
622
623       if (file->crcsignature == NULL) {
624         crcsignature = getcrcsignature(file->d_name, file->size);
625         if (crcsignature == NULL) return NULL;
626
627         file->crcsignature = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
628         if (file->crcsignature == NULL) {
629           errormsg("out of memory\n");
630           exit(1);
631         }
632         md5copy(file->crcsignature, crcsignature);
633       }
634
635       cmpresult = md5cmp(file->crcsignature, checktree->file->crcsignature);
636     }
637   }
638
639   if (cmpresult < 0) {
640     if (checktree->left != NULL) {
641       return checkmatch(root, checktree->left, file);
642     } else {
643       registerfile(&(checktree->left), file);
644       return NULL;
645     }
646   } else if (cmpresult > 0) {
647     if (checktree->right != NULL) {
648       return checkmatch(root, checktree->right, file);
649     } else {
650       registerfile(&(checktree->right), file);
651       return NULL;
652     }
653   } else 
654   {
655     return &checktree->file;
656   }
657 }
658
659 void summarizematches(file_t *files)
660 {
661   int numsets = 0;
662   double numbytes = 0.0;
663   int numfiles = 0;
664   file_t *tmpfile;
665
666   while (files != NULL)
667   {
668     if (files->hasdupes)
669     {
670       numsets++;
671
672       tmpfile = files->duplicates;
673       while (tmpfile != NULL)
674       {
675         numfiles++;
676         numbytes += files->size;
677         tmpfile = tmpfile->duplicates;
678       }
679     }
680
681     files = files->next;
682   }
683
684   if (numsets == 0)
685     printf("No duplicates found.\n\n");
686   else
687   {
688     if (numbytes < 1024.0)
689       printf("%d duplicate files (in %d sets), occupying %.0f bytes.\n\n", numfiles, numsets, numbytes);
690     else if (numbytes <= (1000.0 * 1000.0))
691       printf("%d duplicate files (in %d sets), occupying %.1f kilobytes\n\n", numfiles, numsets, numbytes / 1000.0);
692     else
693       printf("%d duplicate files (in %d sets), occupying %.1f megabytes\n\n", numfiles, numsets, numbytes / (1000.0 * 1000.0));
694  
695   }
696 }
697
698 void printmatches(file_t *files)
699 {
700   file_t *tmpfile;
701
702   while (files != NULL) {
703     if (files->hasdupes) {
704       if (!ISFLAG(flags, F_OMITFIRST)) {
705         if (ISFLAG(flags, F_SHOWSIZE)) printf("%lld byte%seach:\n", (long long int)files->size,
706          (files->size != 1) ? "s " : " ");
707         if (ISFLAG(flags, F_SHOWTIME))
708           printf("%s ", fmttime(files->mtime));
709         if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &files->d_name);
710         printf("%s%c", files->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
711       }
712       tmpfile = files->duplicates;
713       while (tmpfile != NULL) {
714         if (ISFLAG(flags, F_SHOWTIME))
715           printf("%s ", fmttime(tmpfile->mtime));
716         if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &tmpfile->d_name);
717         printf("%s%c", tmpfile->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
718         tmpfile = tmpfile->duplicates;
719       }
720       printf("\n");
721
722     }
723       
724     files = files->next;
725   }
726 }
727
728 /*
729 #define REVISE_APPEND "_tmp"
730 char *revisefilename(char *path, int seq)
731 {
732   int digits;
733   char *newpath;
734   char *scratch;
735   char *dot;
736
737   digits = numdigits(seq);
738   newpath = malloc(strlen(path) + strlen(REVISE_APPEND) + digits + 1);
739   if (!newpath) return newpath;
740
741   scratch = malloc(strlen(path) + 1);
742   if (!scratch) return newpath;
743
744   strcpy(scratch, path);
745   dot = strrchr(scratch, '.');
746   if (dot) 
747   {
748     *dot = 0;
749     sprintf(newpath, "%s%s%d.%s", scratch, REVISE_APPEND, seq, dot + 1);
750   }
751
752   else
753   {
754     sprintf(newpath, "%s%s%d", path, REVISE_APPEND, seq);
755   }
756
757   free(scratch);
758
759   return newpath;
760 } */
761
762 int relink(char *oldfile, char *newfile)
763 {
764   dev_t od;
765   dev_t nd;
766   ino_t oi;
767   ino_t ni;
768
769   od = getdevice(oldfile);
770   oi = getinode(oldfile);
771
772   if (link(oldfile, newfile) != 0)
773     return 0;
774
775   /* make sure we're working with the right file (the one we created) */
776   nd = getdevice(newfile);
777   ni = getinode(newfile);
778
779   if (nd != od || oi != ni)
780     return 0; /* file is not what we expected */
781
782   return 1;
783 }
784
785 void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile)
786 {
787   int counter;
788   int groups = 0;
789   int curgroup = 0;
790   file_t *tmpfile;
791   file_t *curfile;
792   file_t **dupelist;
793   int *preserve;
794   int firstpreserved;
795   char *preservestr;
796   char *token;
797   char *tstr;
798   int number;
799   int sum;
800   int max = 0;
801   int x;
802   int i;
803   struct log_info *loginfo;
804   int log_error;
805   FILE *file1;
806   FILE *file2;
807   int ismatch;
808   char *errorstring;
809
810   curfile = files;
811   
812   while (curfile) {
813     if (curfile->hasdupes) {
814       counter = 1;
815       groups++;
816
817       tmpfile = curfile->duplicates;
818       while (tmpfile) {
819         counter++;
820         tmpfile = tmpfile->duplicates;
821       }
822       
823       if (counter > max) max = counter;
824     }
825     
826     curfile = curfile->next;
827   }
828
829   max++;
830
831   dupelist = (file_t**) malloc(sizeof(file_t*) * max);
832   preserve = (int*) malloc(sizeof(int) * max);
833   preservestr = (char*) malloc(INPUT_SIZE);
834
835   if (!dupelist || !preserve || !preservestr) {
836     errormsg("out of memory\n");
837     exit(1);
838   }
839
840   loginfo = 0;
841   if (logfile != 0)
842     loginfo = log_open(logfile, &log_error);
843
844   register_sigint_handler();
845
846   while (files) {
847     if (files->hasdupes) {
848       curgroup++;
849       counter = 1;
850       dupelist[counter] = files;
851
852       if (prompt) 
853       {
854         if (ISFLAG(flags, F_SHOWTIME))
855           printf("[%d] [%s] %s\n", counter, fmttime(files->mtime), files->d_name);
856         else
857           printf("[%d] %s\n", counter, files->d_name);
858       }
859
860       tmpfile = files->duplicates;
861
862       while (tmpfile) {
863         dupelist[++counter] = tmpfile;
864         if (prompt)
865         {
866           if (ISFLAG(flags, F_SHOWTIME))
867             printf("[%d] [%s] %s\n", counter, fmttime(tmpfile->mtime), tmpfile->d_name);
868           else
869             printf("[%d] %s\n", counter, tmpfile->d_name);
870         }
871         tmpfile = tmpfile->duplicates;
872       }
873
874       if (prompt) printf("\n");
875
876       if (!prompt) /* preserve only the first file */
877       {
878          preserve[1] = 1;
879          for (x = 2; x <= counter; x++) preserve[x] = 0;
880       }
881
882       else /* prompt for files to preserve */
883
884       do {
885         printf("Set %d of %d, preserve files [1 - %d, all, quit]",
886           curgroup, groups, counter);
887         if (ISFLAG(flags, F_SHOWSIZE)) printf(" (%lld byte%seach)", (long long int)files->size,
888           (files->size != 1) ? "s " : " ");
889         printf(": ");
890         fflush(stdout);
891
892         if (!fgets(preservestr, INPUT_SIZE, tty))
893         {
894           preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */
895           preservestr[1] = '\0';
896
897           if (got_sigint)
898           {
899             if (loginfo)
900               log_close(loginfo);
901
902             free(dupelist);
903             free(preserve);
904             free(preservestr);
905
906             printf("\n");
907
908             exit(0);
909           }
910         }
911
912         i = strlen(preservestr) - 1;
913
914         while (preservestr[i]!='\n'){ /* tail of buffer must be a newline */
915           tstr = (char*)
916             realloc(preservestr, strlen(preservestr) + 1 + INPUT_SIZE);
917           if (!tstr) { /* couldn't allocate memory, treat as fatal */
918             errormsg("out of memory!\n");
919             exit(1);
920           }
921
922           preservestr = tstr;
923           if (!fgets(preservestr + i + 1, INPUT_SIZE, tty))
924           {
925             preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */
926             preservestr[1] = '\0';
927             break;
928           }
929           i = strlen(preservestr)-1;
930         }
931
932         if (strcmp(preservestr, "q\n") == 0 || strcmp(preservestr, "quit\n") == 0)
933         {
934           if (loginfo)
935             log_close(loginfo);
936
937           free(dupelist);
938           free(preserve);
939           free(preservestr);
940
941           printf("\n");
942
943           exit(0);
944         }
945
946         for (x = 1; x <= counter; x++) preserve[x] = 0;
947         
948         token = strtok(preservestr, " ,\n");
949         
950         while (token != NULL) {
951           if (strcasecmp(token, "all") == 0 || strcasecmp(token, "a") == 0)
952             for (x = 0; x <= counter; x++) preserve[x] = 1;
953           
954           number = 0;
955           sscanf(token, "%d", &number);
956           if (number > 0 && number <= counter) preserve[number] = 1;
957           
958           token = strtok(NULL, " ,\n");
959         }
960       
961         for (sum = 0, x = 1; x <= counter; x++) sum += preserve[x];
962       } while (sum < 1); /* make sure we've preserved at least one file */
963
964       printf("\n");
965
966       if (loginfo)
967         log_begin_set(loginfo);
968
969       for (x = 1; x <= counter; x++) { 
970         if (preserve[x])
971         {
972           printf("   [+] %s\n", dupelist[x]->d_name);
973
974           if (loginfo)
975             log_file_remaining(loginfo, dupelist[x]->d_name);
976         }
977         else {
978     if (ISFLAG(flags, F_DEFERCONFIRMATION))
979     {
980       firstpreserved = 0;
981       for (i = 1; i <= counter; ++i)
982       {
983         if (preserve[i])
984         {
985           firstpreserved = i;
986           break;
987         }
988       }
989
990       file1 = fopen(dupelist[x]->d_name, "rb");
991       file2 = fopen(dupelist[firstpreserved]->d_name, "rb");
992
993       if (file1 && file2)
994         ismatch = confirmmatch(file1, file2);
995       else
996         ismatch = 0;
997
998       if (file2)
999         fclose(file2);
1000
1001       if (file1)
1002         fclose(file1);
1003     }
1004     else
1005     {
1006       ismatch = 1;
1007     }
1008
1009     if (ismatch) {
1010       if (removeifnotchanged(dupelist[x], &errorstring) == 0) {
1011         printf("   [-] %s\n", dupelist[x]->d_name);
1012
1013         if (loginfo)
1014           log_file_deleted(loginfo, dupelist[x]->d_name);
1015       }
1016       else {
1017         printf("   [!] %s ", dupelist[x]->d_name);
1018         printf("-- unable to delete file: %s!\n", errorstring);
1019
1020         if (loginfo)
1021           log_file_remaining(loginfo, dupelist[x]->d_name);
1022       }
1023     }
1024     else {
1025       printf("   [!] %s\n", dupelist[x]->d_name);
1026       printf(" -- unable to confirm match; file not deleted!\n");
1027
1028       if (loginfo)
1029         log_file_remaining(loginfo, dupelist[x]->d_name);
1030     }
1031         }
1032       }
1033       printf("\n");
1034
1035       if (loginfo)
1036         log_end_set(loginfo);
1037     }
1038     
1039     files = files->next;
1040   }
1041
1042   if (loginfo)
1043     log_close(loginfo);
1044
1045   free(dupelist);
1046   free(preserve);
1047   free(preservestr);
1048 }
1049
1050 int sort_pairs_by_arrival(file_t *f1, file_t *f2)
1051 {
1052   if (f2->duplicates != 0)
1053     return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1054
1055   return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1056 }
1057
1058 int sort_pairs_by_ctime(file_t *f1, file_t *f2)
1059 {
1060   if (f1->ctime < f2->ctime)
1061     return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1062   else if (f1->ctime > f2->ctime)
1063     return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1064
1065   return 0;
1066 }
1067
1068 int sort_pairs_by_mtime(file_t *f1, file_t *f2)
1069 {
1070   if (f1->mtime < f2->mtime)
1071     return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1072   else if (f1->mtime > f2->mtime)
1073     return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1074   else
1075     return sort_pairs_by_ctime(f1, f2);
1076 }
1077
1078 int sort_pairs_by_filename(file_t *f1, file_t *f2)
1079 {
1080   int strvalue = strcmp(f1->d_name, f2->d_name);
1081   return !ISFLAG(flags, F_REVERSE) ? strvalue : -strvalue;
1082 }
1083
1084 void registerpair(file_t **matchlist, file_t *newmatch, 
1085                   int (*comparef)(file_t *f1, file_t *f2))
1086 {
1087   file_t *traverse;
1088   file_t *back;
1089
1090   (*matchlist)->hasdupes = 1;
1091
1092   back = 0;
1093   traverse = *matchlist;
1094   while (traverse)
1095   {
1096     if (comparef(newmatch, traverse) <= 0)
1097     {
1098       newmatch->duplicates = traverse;
1099       
1100       if (back == 0)
1101       {
1102         *matchlist = newmatch; /* update pointer to head of list */
1103
1104         newmatch->hasdupes = 1;
1105         traverse->hasdupes = 0; /* flag is only for first file in dupe chain */
1106       }
1107       else
1108         back->duplicates = newmatch;
1109
1110       break;
1111     }
1112     else
1113     {
1114       if (traverse->duplicates == 0)
1115       {
1116         traverse->duplicates = newmatch;
1117         
1118         if (back == 0)
1119           traverse->hasdupes = 1;
1120         
1121         break;
1122       }
1123     }
1124     
1125     back = traverse;
1126     traverse = traverse->duplicates;
1127   }
1128 }
1129
1130 void deletesuccessor(file_t **existing, file_t *duplicate, int matchconfirmed,
1131       int (*comparef)(file_t *f1, file_t *f2), struct log_info *loginfo)
1132 {
1133   file_t *to_keep;
1134   file_t *to_delete;
1135   char *errorstring;
1136
1137   if (comparef(duplicate, *existing) >= 0)
1138   {
1139     to_keep = *existing;
1140     to_delete = duplicate;
1141   }
1142   else
1143   {
1144     to_keep = duplicate;
1145     to_delete = *existing;
1146
1147     *existing = duplicate;
1148   }
1149
1150   if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1151
1152   if (loginfo)
1153     log_begin_set(loginfo);
1154
1155   printf("   [+] %s\n", to_keep->d_name);
1156
1157   if (loginfo)
1158     log_file_remaining(loginfo, to_keep->d_name);
1159
1160   if (matchconfirmed)
1161   {
1162     if (removeifnotchanged(to_delete, &errorstring) == 0) {
1163       printf("   [-] %s\n", to_delete->d_name);
1164
1165       if (loginfo)
1166         log_file_deleted(loginfo, to_delete->d_name);
1167     } else {
1168       printf("   [!] %s ", to_delete->d_name);
1169       printf("-- unable to delete file: %s!\n", errorstring);
1170
1171       if (loginfo)
1172         log_file_remaining(loginfo, to_delete->d_name);
1173     }
1174   }
1175   else
1176   {
1177     printf("   [!] %s\n", to_delete->d_name);
1178     printf(" -- unable to confirm match; file not deleted!\n");
1179
1180     if (loginfo)
1181       log_file_remaining(loginfo, to_delete->d_name);
1182   }
1183
1184   if (loginfo)
1185     log_end_set(loginfo);
1186
1187   printf("\n");
1188 }
1189
1190 void help_text()
1191 {
1192   printf("Usage: fdupes [options] DIRECTORY...\n\n");
1193
1194   /*     0        1 0       2 0       3 0       4 0       5 0       6 0       7 0       8 0
1195   -------"---------|---------|---------|---------|---------|---------|---------|---------|"
1196   */
1197   printf(" -r --recurse            for every directory given follow subdirectories\n");
1198   printf("                         encountered within\n");
1199   printf(" -R --recurse:           for each directory given after this option follow\n");
1200   printf("                         subdirectories encountered within (note the ':' at the\n");
1201   printf("                         end of the option, manpage for more details)\n");
1202   printf(" -s --symlinks           follow symlinks\n");
1203   printf(" -H --hardlinks          normally, when two or more files point to the same\n");
1204   printf("                         disk area they are treated as non-duplicates; this\n");
1205   printf("                         option will change this behavior\n");
1206   printf(" -G --minsize=SIZE       consider only files greater than or equal to SIZE bytes\n");
1207   printf(" -L --maxsize=SIZE       consider only files less than or equal to SIZE bytes\n");
1208   printf(" -n --noempty            exclude zero-length files from consideration\n");
1209   printf(" -A --nohidden           exclude hidden files from consideration\n");
1210   printf(" -f --omitfirst          omit the first file in each set of matches\n");
1211   printf(" -1 --sameline           list each set of matches on a single line\n");
1212   printf(" -S --size               show size of duplicate files\n");
1213   printf(" -t --time               show modification time of duplicate files\n");
1214   printf(" -m --summarize          summarize dupe information\n");
1215   printf(" -q --quiet              hide progress indicator\n");
1216   printf(" -d --delete             prompt user for files to preserve and delete all\n");
1217   printf("                         others; important: under particular circumstances,\n");
1218   printf("                         data may be lost when using this option together\n");
1219   printf("                         with -s or --symlinks, or when specifying a\n");
1220   printf("                         particular directory more than once; refer to the\n");
1221   printf("                         fdupes documentation for additional information\n");
1222   printf(" -D --deferconfirmation  in interactive mode, defer byte-for-byte confirmation\n");
1223   printf("                         of duplicates until just before file deletion\n");
1224 #ifndef NO_NCURSES
1225   printf(" -P --plain              with --delete, use line-based prompt (as with older\n");
1226   printf("                         versions of fdupes) instead of screen-mode interface\n");
1227 #endif
1228   printf(" -N --noprompt           together with --delete, preserve the first file in\n");
1229   printf("                         each set of duplicates and delete the rest without\n");
1230   printf("                         prompting the user\n");
1231   printf(" -I --immediate          delete duplicates as they are encountered, without\n");
1232   printf("                         grouping into sets; implies --noprompt\n");
1233   printf(" -p --permissions        don't consider files with different owner/group or\n");
1234   printf("                         permission bits as duplicates\n");
1235   printf(" -o --order=BY           select sort order for output and deleting; by file\n");
1236   printf("                         modification time (BY='time'; default), status\n");
1237   printf("                         change time (BY='ctime'), or filename (BY='name')\n");
1238   printf(" -i --reverse            reverse order while sorting\n");
1239   printf(" -l --log=LOGFILE        log file deletion choices to LOGFILE\n");
1240   printf(" -v --version            display fdupes version\n");
1241   printf(" -h --help               display this help message\n\n");
1242 #ifndef HAVE_GETOPT_H
1243   printf("Note: Long options are not supported in this fdupes build.\n\n");
1244 #endif
1245 }
1246
1247 int main(int argc, char **argv) {
1248   int x;
1249   int opt;
1250   FILE *file1;
1251   FILE *file2;
1252   file_t *files = NULL;
1253   file_t *curfile;
1254   file_t **match = NULL;
1255   filetree_t *checktree = NULL;
1256   int filecount = 0;
1257   int progress = 0;
1258   char **oldargv;
1259   int firstrecurse;
1260   char *logfile = 0;
1261   struct log_info *loginfo = NULL;
1262   int log_error;
1263   struct stat logfile_status;
1264   char *endptr;
1265   
1266 #ifdef HAVE_GETOPT_H
1267   static struct option long_options[] = 
1268   {
1269     { "omitfirst", 0, 0, 'f' },
1270     { "recurse", 0, 0, 'r' },
1271     { "recurse:", 0, 0, 'R' },
1272     { "quiet", 0, 0, 'q' },
1273     { "sameline", 0, 0, '1' },
1274     { "size", 0, 0, 'S' },
1275     { "time", 0, 0, 't' },
1276     { "symlinks", 0, 0, 's' },
1277     { "hardlinks", 0, 0, 'H' },
1278     { "minsize", 1, 0, 'G' },
1279     { "maxsize", 1, 0, 'L' },
1280     { "noempty", 0, 0, 'n' },
1281     { "nohidden", 0, 0, 'A' },
1282     { "delete", 0, 0, 'd' },
1283     { "plain", 0, 0, 'P' },
1284     { "version", 0, 0, 'v' },
1285     { "help", 0, 0, 'h' },
1286     { "noprompt", 0, 0, 'N' },
1287     { "immediate", 0, 0, 'I'},
1288     { "summarize", 0, 0, 'm'},
1289     { "summary", 0, 0, 'm' },
1290     { "permissions", 0, 0, 'p' },
1291     { "order", 1, 0, 'o' },
1292     { "reverse", 0, 0, 'i' },
1293     { "log", 1, 0, 'l' },
1294     { "deferconfirmation", 0, 0, 'D' },
1295     { 0, 0, 0, 0 }
1296   };
1297 #define GETOPT getopt_long
1298 #else
1299 #define GETOPT getopt
1300 #endif
1301
1302   program_name = argv[0];
1303
1304   setlocale(LC_CTYPE, "");
1305
1306   oldargv = cloneargs(argc, argv);
1307
1308   while ((opt = GETOPT(argc, argv, "frRq1StsHG:L:nAdPvhNImpo:il:D"
1309 #ifdef HAVE_GETOPT_H
1310           , long_options, NULL
1311 #endif
1312           )) != EOF) {
1313     switch (opt) {
1314     case 'f':
1315       SETFLAG(flags, F_OMITFIRST);
1316       break;
1317     case 'r':
1318       SETFLAG(flags, F_RECURSE);
1319       break;
1320     case 'R':
1321       SETFLAG(flags, F_RECURSEAFTER);
1322       break;
1323     case 'q':
1324       SETFLAG(flags, F_HIDEPROGRESS);
1325       break;
1326     case '1':
1327       SETFLAG(flags, F_DSAMELINE);
1328       break;
1329     case 'S':
1330       SETFLAG(flags, F_SHOWSIZE);
1331       break;
1332     case 't':
1333       SETFLAG(flags, F_SHOWTIME);
1334       break;
1335     case 's':
1336       SETFLAG(flags, F_FOLLOWLINKS);
1337       break;
1338     case 'H':
1339       SETFLAG(flags, F_CONSIDERHARDLINKS);
1340       break;
1341     case 'G':
1342       minsize = strtoll(optarg, &endptr, 10);
1343       if (optarg[0] == '\0' || *endptr != '\0' || minsize < 0)
1344       {
1345         errormsg("invalid value for --minsize: '%s'\n", optarg);
1346         exit(1);
1347       }
1348       break;
1349     case 'L':
1350       maxsize = strtoll(optarg, &endptr, 10);
1351       if (optarg[0] == '\0' || *endptr != '\0' || maxsize < 0)
1352       {
1353         errormsg("invalid value for --maxsize: '%s'\n", optarg);
1354         exit(1);
1355       }
1356       break;
1357     case 'n':
1358       SETFLAG(flags, F_EXCLUDEEMPTY);
1359       break;
1360     case 'A':
1361       SETFLAG(flags, F_EXCLUDEHIDDEN);
1362       break;
1363     case 'd':
1364       SETFLAG(flags, F_DELETEFILES);
1365       break;
1366     case 'P':
1367       SETFLAG(flags, F_PLAINPROMPT);
1368       break;
1369     case 'v':
1370       printf("fdupes %s\n", VERSION);
1371       exit(0);
1372     case 'h':
1373       help_text();
1374       exit(1);
1375     case 'N':
1376       SETFLAG(flags, F_NOPROMPT);
1377       break;
1378     case 'I':
1379       SETFLAG(flags, F_IMMEDIATE);
1380       break;
1381     case 'm':
1382       SETFLAG(flags, F_SUMMARIZEMATCHES);
1383       break;
1384     case 'p':
1385       SETFLAG(flags, F_PERMISSIONS);
1386       break;
1387     case 'o':
1388       if (!strcasecmp("name", optarg)) {
1389         ordertype = ORDER_NAME;
1390       } else if (!strcasecmp("time", optarg)) {
1391         ordertype = ORDER_MTIME;
1392       } else if (!strcasecmp("ctime", optarg)) {
1393         ordertype = ORDER_CTIME;
1394       } else {
1395         errormsg("invalid value for --order: '%s'\n", optarg);
1396         exit(1);
1397       }
1398       break;
1399     case 'i':
1400       SETFLAG(flags, F_REVERSE);
1401       break;
1402     case 'l':
1403       logfile = optarg;
1404       break;
1405     case 'D':
1406       SETFLAG(flags, F_DEFERCONFIRMATION);
1407       break;
1408     default:
1409       fprintf(stderr, "Try `fdupes --help' for more information.\n");
1410       exit(1);
1411     }
1412   }
1413
1414   if (optind >= argc) {
1415     errormsg("no directories specified\n");
1416     exit(1);
1417   }
1418
1419   if (ISFLAG(flags, F_RECURSE) && ISFLAG(flags, F_RECURSEAFTER)) {
1420     errormsg("options --recurse and --recurse: are not compatible\n");
1421     exit(1);
1422   }
1423
1424   if (ISFLAG(flags, F_SUMMARIZEMATCHES) && ISFLAG(flags, F_DELETEFILES)) {
1425     errormsg("options --summarize and --delete are not compatible\n");
1426     exit(1);
1427   }
1428
1429   if (ISFLAG(flags, F_DEFERCONFIRMATION) && (!ISFLAG(flags, F_DELETEFILES) || ISFLAG(flags, F_NOPROMPT)))
1430   {
1431     errormsg("--deferconfirmation only works with interactive deletion modes\n");
1432     exit(1);
1433   }
1434
1435   if (!ISFLAG(flags, F_DELETEFILES))
1436     logfile = 0;
1437
1438   if (logfile != 0)
1439   {
1440     loginfo = log_open(logfile, &log_error);
1441     if (loginfo == 0)
1442     {
1443       if (log_error == LOG_ERROR_NOT_A_LOG_FILE)
1444         errormsg("%s: doesn't look like an fdupes log file\n", logfile);
1445       else
1446         errormsg("%s: could not open log file\n", logfile);
1447
1448       exit(1);
1449     }
1450
1451     if (stat(logfile, &logfile_status) != 0)
1452     {
1453       errormsg("could not read log file status\n");
1454       exit(1);
1455     }
1456   }
1457
1458   if (ISFLAG(flags, F_RECURSEAFTER)) {
1459     firstrecurse = nonoptafter("--recurse:", argc, oldargv, argv, optind);
1460     
1461     if (firstrecurse == argc)
1462       firstrecurse = nonoptafter("-R", argc, oldargv, argv, optind);
1463
1464     if (firstrecurse == argc) {
1465       errormsg("-R option must be isolated from other options\n");
1466       exit(1);
1467     }
1468
1469     /* F_RECURSE is not set for directories before --recurse: */
1470     for (x = optind; x < firstrecurse; x++)
1471       filecount += grokdir(argv[x], &files, logfile ? &logfile_status : 0);
1472
1473     /* Set F_RECURSE for directories after --recurse: */
1474     SETFLAG(flags, F_RECURSE);
1475
1476     for (x = firstrecurse; x < argc; x++)
1477       filecount += grokdir(argv[x], &files, logfile ? &logfile_status : 0);
1478   } else {
1479     for (x = optind; x < argc; x++)
1480       filecount += grokdir(argv[x], &files, logfile ? &logfile_status : 0);
1481   }
1482
1483   if (!files) {
1484     if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1485     exit(0);
1486   }
1487   
1488   curfile = files;
1489
1490   while (curfile) {
1491     if (!checktree) 
1492       registerfile(&checktree, curfile);
1493     else 
1494       match = checkmatch(&checktree, checktree, curfile);
1495
1496     if (match != NULL) {
1497       file1 = fopen(curfile->d_name, "rb");
1498       if (!file1) {
1499         curfile = curfile->next;
1500         continue;
1501       }
1502       
1503       file2 = fopen((*match)->d_name, "rb");
1504       if (!file2) {
1505         fclose(file1);
1506         curfile = curfile->next;
1507         continue;
1508       }
1509
1510       if (ISFLAG(flags, F_DELETEFILES) && ISFLAG(flags, F_IMMEDIATE))
1511       {
1512           deletesuccessor(match, curfile, confirmmatch(file1, file2),
1513               ordertype == ORDER_MTIME ? sort_pairs_by_mtime :
1514               ordertype == ORDER_CTIME ? sort_pairs_by_ctime :
1515                                          sort_pairs_by_filename, loginfo );
1516       }
1517       else if (ISFLAG(flags, F_DEFERCONFIRMATION) || confirmmatch(file1, file2))
1518         registerpair(match, curfile,
1519             ordertype == ORDER_MTIME ? sort_pairs_by_mtime :
1520             ordertype == ORDER_CTIME ? sort_pairs_by_ctime :
1521                                        sort_pairs_by_filename );
1522
1523       fclose(file1);
1524       fclose(file2);
1525     }
1526
1527     curfile = curfile->next;
1528
1529     if (!ISFLAG(flags, F_HIDEPROGRESS)) {
1530       fprintf(stderr, "\rProgress [%d/%d] %d%% ", progress, filecount,
1531        (int)((float) progress / (float) filecount * 100.0));
1532       progress++;
1533     }
1534   }
1535
1536   if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1537
1538   if (loginfo != 0)
1539   {
1540     log_close(loginfo);
1541     loginfo = 0;
1542   }
1543
1544   if (ISFLAG(flags, F_DELETEFILES))
1545   {
1546     if (ISFLAG(flags, F_NOPROMPT) || ISFLAG(flags, F_IMMEDIATE))
1547     {
1548       deletefiles(files, 0, 0, logfile);
1549     }
1550     else
1551     {
1552 #ifndef NO_NCURSES
1553       if (!ISFLAG(flags, F_PLAINPROMPT))
1554       {
1555         if (newterm(getenv("TERM"), stdout, stdin) != 0)
1556         {
1557           deletefiles_ncurses(files, logfile);
1558         }
1559         else
1560         {
1561           errormsg("could not enter screen mode; falling back to plain mode\n\n");
1562           SETFLAG(flags, F_PLAINPROMPT);
1563         }
1564       }
1565
1566       if (ISFLAG(flags, F_PLAINPROMPT))
1567       {
1568         if (freopen("/dev/tty", "r", stdin) == NULL)
1569         {
1570           errormsg("could not open terminal for input\n");
1571           exit(1);
1572         }
1573
1574         deletefiles(files, 1, stdin, logfile);
1575       }
1576 #else
1577       if (freopen("/dev/tty", "r", stdin) == NULL)
1578       {
1579         errormsg("could not open terminal for input\n");
1580         exit(1);
1581       }
1582
1583       deletefiles(files, 1, stdin, logfile);
1584 #endif
1585     }
1586   }
1587
1588   else 
1589
1590     if (ISFLAG(flags, F_SUMMARIZEMATCHES))
1591       summarizematches(files);
1592       
1593     else
1594
1595       printmatches(files);
1596
1597   while (files) {
1598     curfile = files->next;
1599     free(files->d_name);
1600     free(files->crcsignature);
1601     free(files->crcpartial);
1602     free(files);
1603     files = curfile;
1604   }
1605
1606   for (x = 0; x < argc; x++)
1607     free(oldargv[x]);
1608
1609   free(oldargv);
1610
1611   purgetree(checktree);
1612
1613   return 0;
1614 }