"Fossies" - the Fresh Open Source Software Archive

Member "fdupes-2.1.2/fdupes.c" (12 Aug 2020, 39421 Bytes) of package /linux/privat/fdupes-2.1.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "fdupes.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.1.1_vs_2.1.2.

    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 }