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