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