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