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