1 /* FDUPES Copyright (c) 1999-2018 Adrian Lopez
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:
11 The above copyright notice and this permission notice shall be
12 included in all copies or substantial portions of the Software.
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. */
39 #ifdef HAVE_NCURSESW_CURSES_H
40 #include <ncursesw/curses.h>
44 #include "ncurses-interface.h"
47 #include "confirmmatch.h"
52 #include "removeifnotchanged.h"
54 long long minsize = -1;
55 long long maxsize = -1;
65 ordertype_t ordertype = ORDER_MTIME;
67 #define MD5_DIGEST_LENGTH 16
69 typedef struct _filetree {
71 struct _filetree *left;
72 struct _filetree *right;
75 void escapefilename(char *escape_list, char **filename_ptr)
82 filename = *filename_ptr;
84 tmp = (char*) malloc(strlen(filename) * 2 + 1);
86 errormsg("out of memory!\n");
90 for (x = 0, tx = 0; x < strlen(filename); x++) {
91 if (strchr(escape_list, filename[x]) != NULL) tmp[tx++] = '\\';
92 tmp[tx++] = filename[x];
98 *filename_ptr = realloc(*filename_ptr, strlen(tmp) + 1);
99 if (*filename_ptr == NULL) {
100 errormsg("out of memory!\n");
103 strcpy(*filename_ptr, tmp);
107 dev_t getdevice(char *filename) {
110 if (stat(filename, &s) != 0) return 0;
115 ino_t getinode(char *filename) {
118 if (stat(filename, &s) != 0) return 0;
123 char *fmttime(time_t t) {
126 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", localtime(&t));
131 char **cloneargs(int argc, char **argv)
136 args = (char **) malloc(sizeof(char*) * argc);
138 errormsg("out of memory!\n");
142 for (x = 0; x < argc; x++) {
143 args[x] = (char*) malloc(strlen(argv[x]) + 1);
144 if (args[x] == NULL) {
146 errormsg("out of memory!\n");
150 strcpy(args[x], argv[x]);
156 int findarg(char *arg, int start, int argc, char **argv)
160 for (x = start; x < argc; x++)
161 if (strcmp(argv[x], arg) == 0)
167 /* Find the first non-option argument after specified option. */
168 int nonoptafter(char *option, int argc, char **oldargv,
169 char **newargv, int optind)
176 targetind = findarg(option, 1, argc, oldargv);
178 for (x = optind; x < argc; x++) {
179 testind = findarg(newargv[x], startat, argc, oldargv);
180 if (testind > targetind) return x;
181 else startat = testind;
187 void getfilestats(file_t *file, struct stat *info, struct stat *linfo)
189 file->size = info->st_size;;
190 file->inode = info->st_ino;
191 file->device = info->st_dev;
192 file->ctime = info->st_ctime;
193 file->mtime = info->st_mtime;
196 int grokdir(char *dir, file_t **filelistp, struct stat *logfile_status)
200 struct dirent *dirinfo;
205 static int progress = 0;
206 static char indicator[] = "-\\|/";
207 char *fullname, *name;
212 errormsg("could not chdir to %s\n", dir);
216 while ((dirinfo = readdir(cd)) != NULL) {
217 if (strcmp(dirinfo->d_name, ".") && strcmp(dirinfo->d_name, "..")) {
218 if (!ISFLAG(flags, F_HIDEPROGRESS)) {
219 fprintf(stderr, "\rBuilding file list %c ", indicator[progress]);
220 progress = (progress + 1) % 4;
223 newfile = (file_t*) malloc(sizeof(file_t));
226 errormsg("out of memory!\n");
229 } else newfile->next = *filelistp;
233 newfile->crcsignature = NULL;
234 newfile->crcpartial = NULL;
235 newfile->duplicates = NULL;
236 newfile->hasdupes = 0;
238 newfile->d_name = (char*)malloc(strlen(dir)+strlen(dirinfo->d_name)+2);
240 if (!newfile->d_name) {
241 errormsg("out of memory!\n");
247 strcpy(newfile->d_name, dir);
248 lastchar = strlen(dir) - 1;
249 if (lastchar >= 0 && dir[lastchar] != '/')
250 strcat(newfile->d_name, "/");
251 strcat(newfile->d_name, dirinfo->d_name);
253 if (ISFLAG(flags, F_EXCLUDEHIDDEN)) {
254 fullname = strdup(newfile->d_name);
257 errormsg("out of memory!\n");
262 name = basename(fullname);
263 if (name[0] == '.' && strcmp(name, ".") && strcmp(name, "..") ) {
264 free(newfile->d_name);
272 if (stat(newfile->d_name, &info) == -1) {
273 free(newfile->d_name);
278 if (!S_ISDIR(info.st_mode) && (((info.st_size == 0 && ISFLAG(flags, F_EXCLUDEEMPTY)) || info.st_size < minsize || (info.st_size > maxsize && maxsize != -1)))) {
279 free(newfile->d_name);
285 if (info.st_dev == logfile_status->st_dev && info.st_ino == logfile_status->st_ino)
287 free(newfile->d_name);
292 if (lstat(newfile->d_name, &linfo) == -1) {
293 free(newfile->d_name);
298 if (S_ISDIR(info.st_mode)) {
299 if (ISFLAG(flags, F_RECURSE) && (ISFLAG(flags, F_FOLLOWLINKS) || !S_ISLNK(linfo.st_mode)))
300 filecount += grokdir(newfile->d_name, filelistp, logfile_status);
301 free(newfile->d_name);
304 if (S_ISREG(linfo.st_mode) || (S_ISLNK(linfo.st_mode) && ISFLAG(flags, F_FOLLOWLINKS))) {
305 getfilestats(newfile, &info, &linfo);
306 *filelistp = newfile;
309 free(newfile->d_name);
321 md5_byte_t *getcrcsignatureuntil(char *filename, off_t fsize, off_t max_read)
325 static md5_byte_t digest[MD5_DIGEST_LENGTH];
326 static md5_byte_t chunk[CHUNK_SIZE];
331 if (max_read != 0 && fsize > max_read)
334 file = fopen(filename, "rb");
336 errormsg("error opening file %s\n", filename);
341 toread = (fsize >= CHUNK_SIZE) ? CHUNK_SIZE : fsize;
342 if (fread(chunk, toread, 1, file) != 1) {
343 errormsg("error reading from file %s\n", filename);
347 md5_append(&state, chunk, toread);
351 md5_finish(&state, digest);
358 md5_byte_t *getcrcsignature(char *filename, off_t fsize)
360 return getcrcsignatureuntil(filename, fsize, 0);
363 md5_byte_t *getcrcpartialsignature(char *filename, off_t fsize)
365 return getcrcsignatureuntil(filename, fsize, PARTIAL_MD5_SIZE);
368 int md5cmp(const md5_byte_t *a, const md5_byte_t *b)
372 for (x = 0; x < MD5_DIGEST_LENGTH; ++x)
376 else if (a[x] > b[x])
383 void md5copy(md5_byte_t *to, const md5_byte_t *from)
387 for (x = 0; x < MD5_DIGEST_LENGTH; ++x)
391 void purgetree(filetree_t *checktree)
393 if (checktree->left != NULL) purgetree(checktree->left);
395 if (checktree->right != NULL) purgetree(checktree->right);
400 int registerfile(filetree_t **branch, file_t *file)
402 *branch = (filetree_t*) malloc(sizeof(filetree_t));
403 if (*branch == NULL) {
404 errormsg("out of memory!\n");
408 (*branch)->file = file;
409 (*branch)->left = NULL;
410 (*branch)->right = NULL;
415 int same_permissions(char* name1, char* name2)
419 if (stat(name1, &s1) != 0) return -1;
420 if (stat(name2, &s2) != 0) return -1;
422 return (s1.st_mode == s2.st_mode &&
423 s1.st_uid == s2.st_uid &&
424 s1.st_gid == s2.st_gid);
427 int is_hardlink(filetree_t *checktree, file_t *file)
431 if ((file->inode == checktree->file->inode) &&
432 (file->device == checktree->file->device))
435 if (checktree->file->hasdupes)
437 dupe = checktree->file->duplicates;
440 if ((file->inode == dupe->inode) &&
441 (file->device == dupe->device))
444 dupe = dupe->duplicates;
445 } while (dupe != NULL);
451 /* check whether two paths represent the same file (deleting one would delete the other) */
452 int is_same_file(file_t *file_a, file_t *file_b)
460 struct stat dirstat_a;
461 struct stat dirstat_b;
463 /* if files on different devices and/or different inodes, they are not the same file */
464 if (file_a->device != file_b->device || file_a->inode != file_b->inode)
467 /* copy filenames (basename and dirname may modify these) */
468 filename_a = strdup(file_a->d_name);
472 filename_b = strdup(file_b->d_name);
476 /* get file basenames */
477 basename_a = basename(filename_a);
478 memmove(filename_a, basename_a, strlen(basename_a) + 1);
480 basename_b = basename(filename_b);
481 memmove(filename_b, basename_b, strlen(basename_b) + 1);
483 /* if files have different names, they are not the same file */
484 if (strcmp(filename_a, filename_b) != 0)
492 strcpy(filename_a, file_a->d_name);
493 strcpy(filename_b, file_b->d_name);
495 /* get directory names */
496 dirname_a = dirname(filename_a);
497 if (stat(dirname_a, &dirstat_a) != 0)
504 dirname_b = dirname(filename_b);
505 if (stat(dirname_b, &dirstat_b) != 0)
515 /* if directories on which files reside are different, they are not the same file */
516 if (dirstat_a.st_dev != dirstat_b.st_dev || dirstat_a.st_ino != dirstat_b.st_ino)
519 /* same device, inode, filename, and directory; therefore, same file */
523 /* check whether given tree node already contains a copy of given file */
524 int has_same_file(filetree_t *checktree, file_t *file)
528 if (is_same_file(checktree->file, file))
531 if (checktree->file->hasdupes)
533 dupe = checktree->file->duplicates;
536 if (is_same_file(dupe, file))
539 dupe = dupe->duplicates;
540 } while (dupe != NULL);
546 file_t **checkmatch(filetree_t **root, filetree_t *checktree, file_t *file)
549 md5_byte_t *crcsignature;
551 if (ISFLAG(flags, F_CONSIDERHARDLINKS))
553 /* If node already contains file, we don't want to add it again.
555 if (has_same_file(checktree, file))
560 /* If device and inode fields are equal one of the files is a
561 hard link to the other or the files have been listed twice
562 unintentionally. We don't want to flag these files as
563 duplicates unless the user specifies otherwise.
565 if (is_hardlink(checktree, file))
569 if (file->size < checktree->file->size)
572 if (file->size > checktree->file->size) cmpresult = 1;
574 if (ISFLAG(flags, F_PERMISSIONS) &&
575 !same_permissions(file->d_name, checktree->file->d_name))
578 if (checktree->file->crcpartial == NULL) {
579 crcsignature = getcrcpartialsignature(checktree->file->d_name, checktree->file->size);
580 if (crcsignature == NULL) {
581 errormsg ("cannot read file %s\n", checktree->file->d_name);
585 checktree->file->crcpartial = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
586 if (checktree->file->crcpartial == NULL) {
587 errormsg("out of memory\n");
590 md5copy(checktree->file->crcpartial, crcsignature);
593 if (file->crcpartial == NULL) {
594 crcsignature = getcrcpartialsignature(file->d_name, file->size);
595 if (crcsignature == NULL) {
596 errormsg ("cannot read file %s\n", file->d_name);
600 file->crcpartial = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
601 if (file->crcpartial == NULL) {
602 errormsg("out of memory\n");
605 md5copy(file->crcpartial, crcsignature);
608 cmpresult = md5cmp(file->crcpartial, checktree->file->crcpartial);
610 if (cmpresult == 0) {
611 if (checktree->file->crcsignature == NULL) {
612 crcsignature = getcrcsignature(checktree->file->d_name, checktree->file->size);
613 if (crcsignature == NULL) return NULL;
615 checktree->file->crcsignature = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
616 if (checktree->file->crcsignature == NULL) {
617 errormsg("out of memory\n");
620 md5copy(checktree->file->crcsignature, crcsignature);
623 if (file->crcsignature == NULL) {
624 crcsignature = getcrcsignature(file->d_name, file->size);
625 if (crcsignature == NULL) return NULL;
627 file->crcsignature = (md5_byte_t*) malloc(MD5_DIGEST_LENGTH * sizeof(md5_byte_t));
628 if (file->crcsignature == NULL) {
629 errormsg("out of memory\n");
632 md5copy(file->crcsignature, crcsignature);
635 cmpresult = md5cmp(file->crcsignature, checktree->file->crcsignature);
640 if (checktree->left != NULL) {
641 return checkmatch(root, checktree->left, file);
643 registerfile(&(checktree->left), file);
646 } else if (cmpresult > 0) {
647 if (checktree->right != NULL) {
648 return checkmatch(root, checktree->right, file);
650 registerfile(&(checktree->right), file);
655 return &checktree->file;
659 void summarizematches(file_t *files)
662 double numbytes = 0.0;
666 while (files != NULL)
672 tmpfile = files->duplicates;
673 while (tmpfile != NULL)
676 numbytes += files->size;
677 tmpfile = tmpfile->duplicates;
685 printf("No duplicates found.\n\n");
688 if (numbytes < 1024.0)
689 printf("%d duplicate files (in %d sets), occupying %.0f bytes.\n\n", numfiles, numsets, numbytes);
690 else if (numbytes <= (1000.0 * 1000.0))
691 printf("%d duplicate files (in %d sets), occupying %.1f kilobytes\n\n", numfiles, numsets, numbytes / 1000.0);
693 printf("%d duplicate files (in %d sets), occupying %.1f megabytes\n\n", numfiles, numsets, numbytes / (1000.0 * 1000.0));
698 void printmatches(file_t *files)
702 while (files != NULL) {
703 if (files->hasdupes) {
704 if (!ISFLAG(flags, F_OMITFIRST)) {
705 if (ISFLAG(flags, F_SHOWSIZE)) printf("%lld byte%seach:\n", (long long int)files->size,
706 (files->size != 1) ? "s " : " ");
707 if (ISFLAG(flags, F_SHOWTIME))
708 printf("%s ", fmttime(files->mtime));
709 if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &files->d_name);
710 printf("%s%c", files->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
712 tmpfile = files->duplicates;
713 while (tmpfile != NULL) {
714 if (ISFLAG(flags, F_SHOWTIME))
715 printf("%s ", fmttime(tmpfile->mtime));
716 if (ISFLAG(flags, F_DSAMELINE)) escapefilename("\\ ", &tmpfile->d_name);
717 printf("%s%c", tmpfile->d_name, ISFLAG(flags, F_DSAMELINE)?' ':'\n');
718 tmpfile = tmpfile->duplicates;
729 #define REVISE_APPEND "_tmp"
730 char *revisefilename(char *path, int seq)
737 digits = numdigits(seq);
738 newpath = malloc(strlen(path) + strlen(REVISE_APPEND) + digits + 1);
739 if (!newpath) return newpath;
741 scratch = malloc(strlen(path) + 1);
742 if (!scratch) return newpath;
744 strcpy(scratch, path);
745 dot = strrchr(scratch, '.');
749 sprintf(newpath, "%s%s%d.%s", scratch, REVISE_APPEND, seq, dot + 1);
754 sprintf(newpath, "%s%s%d", path, REVISE_APPEND, seq);
762 int relink(char *oldfile, char *newfile)
769 od = getdevice(oldfile);
770 oi = getinode(oldfile);
772 if (link(oldfile, newfile) != 0)
775 /* make sure we're working with the right file (the one we created) */
776 nd = getdevice(newfile);
777 ni = getinode(newfile);
779 if (nd != od || oi != ni)
780 return 0; /* file is not what we expected */
785 void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile)
803 struct log_info *loginfo;
813 if (curfile->hasdupes) {
817 tmpfile = curfile->duplicates;
820 tmpfile = tmpfile->duplicates;
823 if (counter > max) max = counter;
826 curfile = curfile->next;
831 dupelist = (file_t**) malloc(sizeof(file_t*) * max);
832 preserve = (int*) malloc(sizeof(int) * max);
833 preservestr = (char*) malloc(INPUT_SIZE);
835 if (!dupelist || !preserve || !preservestr) {
836 errormsg("out of memory\n");
842 loginfo = log_open(logfile, &log_error);
844 register_sigint_handler();
847 if (files->hasdupes) {
850 dupelist[counter] = files;
854 if (ISFLAG(flags, F_SHOWTIME))
855 printf("[%d] [%s] %s\n", counter, fmttime(files->mtime), files->d_name);
857 printf("[%d] %s\n", counter, files->d_name);
860 tmpfile = files->duplicates;
863 dupelist[++counter] = tmpfile;
866 if (ISFLAG(flags, F_SHOWTIME))
867 printf("[%d] [%s] %s\n", counter, fmttime(tmpfile->mtime), tmpfile->d_name);
869 printf("[%d] %s\n", counter, tmpfile->d_name);
871 tmpfile = tmpfile->duplicates;
874 if (prompt) printf("\n");
876 if (!prompt) /* preserve only the first file */
879 for (x = 2; x <= counter; x++) preserve[x] = 0;
882 else /* prompt for files to preserve */
885 printf("Set %d of %d, preserve files [1 - %d, all, quit]",
886 curgroup, groups, counter);
887 if (ISFLAG(flags, F_SHOWSIZE)) printf(" (%lld byte%seach)", (long long int)files->size,
888 (files->size != 1) ? "s " : " ");
892 if (!fgets(preservestr, INPUT_SIZE, tty))
894 preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */
895 preservestr[1] = '\0';
912 i = strlen(preservestr) - 1;
914 while (preservestr[i]!='\n'){ /* tail of buffer must be a newline */
916 realloc(preservestr, strlen(preservestr) + 1 + INPUT_SIZE);
917 if (!tstr) { /* couldn't allocate memory, treat as fatal */
918 errormsg("out of memory!\n");
923 if (!fgets(preservestr + i + 1, INPUT_SIZE, tty))
925 preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */
926 preservestr[1] = '\0';
929 i = strlen(preservestr)-1;
932 if (strcmp(preservestr, "q\n") == 0 || strcmp(preservestr, "quit\n") == 0)
946 for (x = 1; x <= counter; x++) preserve[x] = 0;
948 token = strtok(preservestr, " ,\n");
950 while (token != NULL) {
951 if (strcasecmp(token, "all") == 0 || strcasecmp(token, "a") == 0)
952 for (x = 0; x <= counter; x++) preserve[x] = 1;
955 sscanf(token, "%d", &number);
956 if (number > 0 && number <= counter) preserve[number] = 1;
958 token = strtok(NULL, " ,\n");
961 for (sum = 0, x = 1; x <= counter; x++) sum += preserve[x];
962 } while (sum < 1); /* make sure we've preserved at least one file */
967 log_begin_set(loginfo);
969 for (x = 1; x <= counter; x++) {
972 printf(" [+] %s\n", dupelist[x]->d_name);
975 log_file_remaining(loginfo, dupelist[x]->d_name);
978 if (ISFLAG(flags, F_DEFERCONFIRMATION))
981 for (i = 1; i <= counter; ++i)
990 file1 = fopen(dupelist[x]->d_name, "rb");
991 file2 = fopen(dupelist[firstpreserved]->d_name, "rb");
994 ismatch = confirmmatch(file1, file2);
1010 if (removeifnotchanged(dupelist[x], &errorstring) == 0) {
1011 printf(" [-] %s\n", dupelist[x]->d_name);
1014 log_file_deleted(loginfo, dupelist[x]->d_name);
1017 printf(" [!] %s ", dupelist[x]->d_name);
1018 printf("-- unable to delete file: %s!\n", errorstring);
1021 log_file_remaining(loginfo, dupelist[x]->d_name);
1025 printf(" [!] %s\n", dupelist[x]->d_name);
1026 printf(" -- unable to confirm match; file not deleted!\n");
1029 log_file_remaining(loginfo, dupelist[x]->d_name);
1036 log_end_set(loginfo);
1039 files = files->next;
1050 int sort_pairs_by_arrival(file_t *f1, file_t *f2)
1052 if (f2->duplicates != 0)
1053 return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1055 return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1058 int sort_pairs_by_ctime(file_t *f1, file_t *f2)
1060 if (f1->ctime < f2->ctime)
1061 return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1062 else if (f1->ctime > f2->ctime)
1063 return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1068 int sort_pairs_by_mtime(file_t *f1, file_t *f2)
1070 if (f1->mtime < f2->mtime)
1071 return !ISFLAG(flags, F_REVERSE) ? -1 : 1;
1072 else if (f1->mtime > f2->mtime)
1073 return !ISFLAG(flags, F_REVERSE) ? 1 : -1;
1075 return sort_pairs_by_ctime(f1, f2);
1078 int sort_pairs_by_filename(file_t *f1, file_t *f2)
1080 int strvalue = strcmp(f1->d_name, f2->d_name);
1081 return !ISFLAG(flags, F_REVERSE) ? strvalue : -strvalue;
1084 void registerpair(file_t **matchlist, file_t *newmatch,
1085 int (*comparef)(file_t *f1, file_t *f2))
1090 (*matchlist)->hasdupes = 1;
1093 traverse = *matchlist;
1096 if (comparef(newmatch, traverse) <= 0)
1098 newmatch->duplicates = traverse;
1102 *matchlist = newmatch; /* update pointer to head of list */
1104 newmatch->hasdupes = 1;
1105 traverse->hasdupes = 0; /* flag is only for first file in dupe chain */
1108 back->duplicates = newmatch;
1114 if (traverse->duplicates == 0)
1116 traverse->duplicates = newmatch;
1119 traverse->hasdupes = 1;
1126 traverse = traverse->duplicates;
1130 void deletesuccessor(file_t **existing, file_t *duplicate, int matchconfirmed,
1131 int (*comparef)(file_t *f1, file_t *f2), struct log_info *loginfo)
1137 if (comparef(duplicate, *existing) >= 0)
1139 to_keep = *existing;
1140 to_delete = duplicate;
1144 to_keep = duplicate;
1145 to_delete = *existing;
1147 *existing = duplicate;
1150 if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1153 log_begin_set(loginfo);
1155 printf(" [+] %s\n", to_keep->d_name);
1158 log_file_remaining(loginfo, to_keep->d_name);
1162 if (removeifnotchanged(to_delete, &errorstring) == 0) {
1163 printf(" [-] %s\n", to_delete->d_name);
1166 log_file_deleted(loginfo, to_delete->d_name);
1168 printf(" [!] %s ", to_delete->d_name);
1169 printf("-- unable to delete file: %s!\n", errorstring);
1172 log_file_remaining(loginfo, to_delete->d_name);
1177 printf(" [!] %s\n", to_delete->d_name);
1178 printf(" -- unable to confirm match; file not deleted!\n");
1181 log_file_remaining(loginfo, to_delete->d_name);
1185 log_end_set(loginfo);
1192 printf("Usage: fdupes [options] DIRECTORY...\n\n");
1194 /* 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0
1195 -------"---------|---------|---------|---------|---------|---------|---------|---------|"
1197 printf(" -r --recurse for every directory given follow subdirectories\n");
1198 printf(" encountered within\n");
1199 printf(" -R --recurse: for each directory given after this option follow\n");
1200 printf(" subdirectories encountered within (note the ':' at the\n");
1201 printf(" end of the option, manpage for more details)\n");
1202 printf(" -s --symlinks follow symlinks\n");
1203 printf(" -H --hardlinks normally, when two or more files point to the same\n");
1204 printf(" disk area they are treated as non-duplicates; this\n");
1205 printf(" option will change this behavior\n");
1206 printf(" -G --minsize=SIZE consider only files greater than or equal to SIZE bytes\n");
1207 printf(" -L --maxsize=SIZE consider only files less than or equal to SIZE bytes\n");
1208 printf(" -n --noempty exclude zero-length files from consideration\n");
1209 printf(" -A --nohidden exclude hidden files from consideration\n");
1210 printf(" -f --omitfirst omit the first file in each set of matches\n");
1211 printf(" -1 --sameline list each set of matches on a single line\n");
1212 printf(" -S --size show size of duplicate files\n");
1213 printf(" -t --time show modification time of duplicate files\n");
1214 printf(" -m --summarize summarize dupe information\n");
1215 printf(" -q --quiet hide progress indicator\n");
1216 printf(" -d --delete prompt user for files to preserve and delete all\n");
1217 printf(" others; important: under particular circumstances,\n");
1218 printf(" data may be lost when using this option together\n");
1219 printf(" with -s or --symlinks, or when specifying a\n");
1220 printf(" particular directory more than once; refer to the\n");
1221 printf(" fdupes documentation for additional information\n");
1222 printf(" -D --deferconfirmation in interactive mode, defer byte-for-byte confirmation\n");
1223 printf(" of duplicates until just before file deletion\n");
1225 printf(" -P --plain with --delete, use line-based prompt (as with older\n");
1226 printf(" versions of fdupes) instead of screen-mode interface\n");
1228 printf(" -N --noprompt together with --delete, preserve the first file in\n");
1229 printf(" each set of duplicates and delete the rest without\n");
1230 printf(" prompting the user\n");
1231 printf(" -I --immediate delete duplicates as they are encountered, without\n");
1232 printf(" grouping into sets; implies --noprompt\n");
1233 printf(" -p --permissions don't consider files with different owner/group or\n");
1234 printf(" permission bits as duplicates\n");
1235 printf(" -o --order=BY select sort order for output and deleting; by file\n");
1236 printf(" modification time (BY='time'; default), status\n");
1237 printf(" change time (BY='ctime'), or filename (BY='name')\n");
1238 printf(" -i --reverse reverse order while sorting\n");
1239 printf(" -l --log=LOGFILE log file deletion choices to LOGFILE\n");
1240 printf(" -v --version display fdupes version\n");
1241 printf(" -h --help display this help message\n\n");
1242 #ifndef HAVE_GETOPT_H
1243 printf("Note: Long options are not supported in this fdupes build.\n\n");
1247 int main(int argc, char **argv) {
1252 file_t *files = NULL;
1254 file_t **match = NULL;
1255 filetree_t *checktree = NULL;
1261 struct log_info *loginfo = NULL;
1263 struct stat logfile_status;
1266 #ifdef HAVE_GETOPT_H
1267 static struct option long_options[] =
1269 { "omitfirst", 0, 0, 'f' },
1270 { "recurse", 0, 0, 'r' },
1271 { "recurse:", 0, 0, 'R' },
1272 { "quiet", 0, 0, 'q' },
1273 { "sameline", 0, 0, '1' },
1274 { "size", 0, 0, 'S' },
1275 { "time", 0, 0, 't' },
1276 { "symlinks", 0, 0, 's' },
1277 { "hardlinks", 0, 0, 'H' },
1278 { "minsize", 1, 0, 'G' },
1279 { "maxsize", 1, 0, 'L' },
1280 { "noempty", 0, 0, 'n' },
1281 { "nohidden", 0, 0, 'A' },
1282 { "delete", 0, 0, 'd' },
1283 { "plain", 0, 0, 'P' },
1284 { "version", 0, 0, 'v' },
1285 { "help", 0, 0, 'h' },
1286 { "noprompt", 0, 0, 'N' },
1287 { "immediate", 0, 0, 'I'},
1288 { "summarize", 0, 0, 'm'},
1289 { "summary", 0, 0, 'm' },
1290 { "permissions", 0, 0, 'p' },
1291 { "order", 1, 0, 'o' },
1292 { "reverse", 0, 0, 'i' },
1293 { "log", 1, 0, 'l' },
1294 { "deferconfirmation", 0, 0, 'D' },
1297 #define GETOPT getopt_long
1299 #define GETOPT getopt
1302 program_name = argv[0];
1304 setlocale(LC_CTYPE, "");
1306 oldargv = cloneargs(argc, argv);
1308 while ((opt = GETOPT(argc, argv, "frRq1StsHG:L:nAdPvhNImpo:il:D"
1309 #ifdef HAVE_GETOPT_H
1310 , long_options, NULL
1315 SETFLAG(flags, F_OMITFIRST);
1318 SETFLAG(flags, F_RECURSE);
1321 SETFLAG(flags, F_RECURSEAFTER);
1324 SETFLAG(flags, F_HIDEPROGRESS);
1327 SETFLAG(flags, F_DSAMELINE);
1330 SETFLAG(flags, F_SHOWSIZE);
1333 SETFLAG(flags, F_SHOWTIME);
1336 SETFLAG(flags, F_FOLLOWLINKS);
1339 SETFLAG(flags, F_CONSIDERHARDLINKS);
1342 minsize = strtoll(optarg, &endptr, 10);
1343 if (optarg[0] == '\0' || *endptr != '\0' || minsize < 0)
1345 errormsg("invalid value for --minsize: '%s'\n", optarg);
1350 maxsize = strtoll(optarg, &endptr, 10);
1351 if (optarg[0] == '\0' || *endptr != '\0' || maxsize < 0)
1353 errormsg("invalid value for --maxsize: '%s'\n", optarg);
1358 SETFLAG(flags, F_EXCLUDEEMPTY);
1361 SETFLAG(flags, F_EXCLUDEHIDDEN);
1364 SETFLAG(flags, F_DELETEFILES);
1367 SETFLAG(flags, F_PLAINPROMPT);
1370 printf("fdupes %s\n", VERSION);
1376 SETFLAG(flags, F_NOPROMPT);
1379 SETFLAG(flags, F_IMMEDIATE);
1382 SETFLAG(flags, F_SUMMARIZEMATCHES);
1385 SETFLAG(flags, F_PERMISSIONS);
1388 if (!strcasecmp("name", optarg)) {
1389 ordertype = ORDER_NAME;
1390 } else if (!strcasecmp("time", optarg)) {
1391 ordertype = ORDER_MTIME;
1392 } else if (!strcasecmp("ctime", optarg)) {
1393 ordertype = ORDER_CTIME;
1395 errormsg("invalid value for --order: '%s'\n", optarg);
1400 SETFLAG(flags, F_REVERSE);
1406 SETFLAG(flags, F_DEFERCONFIRMATION);
1409 fprintf(stderr, "Try `fdupes --help' for more information.\n");
1414 if (optind >= argc) {
1415 errormsg("no directories specified\n");
1419 if (ISFLAG(flags, F_RECURSE) && ISFLAG(flags, F_RECURSEAFTER)) {
1420 errormsg("options --recurse and --recurse: are not compatible\n");
1424 if (ISFLAG(flags, F_SUMMARIZEMATCHES) && ISFLAG(flags, F_DELETEFILES)) {
1425 errormsg("options --summarize and --delete are not compatible\n");
1429 if (ISFLAG(flags, F_DEFERCONFIRMATION) && (!ISFLAG(flags, F_DELETEFILES) || ISFLAG(flags, F_NOPROMPT)))
1431 errormsg("--deferconfirmation only works with interactive deletion modes\n");
1435 if (!ISFLAG(flags, F_DELETEFILES))
1440 loginfo = log_open(logfile, &log_error);
1443 if (log_error == LOG_ERROR_NOT_A_LOG_FILE)
1444 errormsg("%s: doesn't look like an fdupes log file\n", logfile);
1446 errormsg("%s: could not open log file\n", logfile);
1451 if (stat(logfile, &logfile_status) != 0)
1453 errormsg("could not read log file status\n");
1458 if (ISFLAG(flags, F_RECURSEAFTER)) {
1459 firstrecurse = nonoptafter("--recurse:", argc, oldargv, argv, optind);
1461 if (firstrecurse == argc)
1462 firstrecurse = nonoptafter("-R", argc, oldargv, argv, optind);
1464 if (firstrecurse == argc) {
1465 errormsg("-R option must be isolated from other options\n");
1469 /* F_RECURSE is not set for directories before --recurse: */
1470 for (x = optind; x < firstrecurse; x++)
1471 filecount += grokdir(argv[x], &files, &logfile_status);
1473 /* Set F_RECURSE for directories after --recurse: */
1474 SETFLAG(flags, F_RECURSE);
1476 for (x = firstrecurse; x < argc; x++)
1477 filecount += grokdir(argv[x], &files, &logfile_status);
1479 for (x = optind; x < argc; x++)
1480 filecount += grokdir(argv[x], &files, &logfile_status);
1484 if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1492 registerfile(&checktree, curfile);
1494 match = checkmatch(&checktree, checktree, curfile);
1496 if (match != NULL) {
1497 file1 = fopen(curfile->d_name, "rb");
1499 curfile = curfile->next;
1503 file2 = fopen((*match)->d_name, "rb");
1506 curfile = curfile->next;
1510 if (ISFLAG(flags, F_DELETEFILES) && ISFLAG(flags, F_IMMEDIATE))
1512 deletesuccessor(match, curfile, confirmmatch(file1, file2),
1513 ordertype == ORDER_MTIME ? sort_pairs_by_mtime :
1514 ordertype == ORDER_CTIME ? sort_pairs_by_ctime :
1515 sort_pairs_by_filename, loginfo );
1517 else if (ISFLAG(flags, F_DEFERCONFIRMATION) || confirmmatch(file1, file2))
1518 registerpair(match, curfile,
1519 ordertype == ORDER_MTIME ? sort_pairs_by_mtime :
1520 ordertype == ORDER_CTIME ? sort_pairs_by_ctime :
1521 sort_pairs_by_filename );
1527 curfile = curfile->next;
1529 if (!ISFLAG(flags, F_HIDEPROGRESS)) {
1530 fprintf(stderr, "\rProgress [%d/%d] %d%% ", progress, filecount,
1531 (int)((float) progress / (float) filecount * 100.0));
1536 if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " ");
1544 if (ISFLAG(flags, F_DELETEFILES))
1546 if (ISFLAG(flags, F_NOPROMPT) || ISFLAG(flags, F_IMMEDIATE))
1548 deletefiles(files, 0, 0, logfile);
1553 if (!ISFLAG(flags, F_PLAINPROMPT))
1555 if (newterm(getenv("TERM"), stdout, stdin) != 0)
1557 deletefiles_ncurses(files, logfile);
1561 errormsg("could not enter screen mode; falling back to plain mode\n\n");
1562 SETFLAG(flags, F_PLAINPROMPT);
1566 if (ISFLAG(flags, F_PLAINPROMPT))
1568 if (freopen("/dev/tty", "r", stdin) == NULL)
1570 errormsg("could not open terminal for input\n");
1574 deletefiles(files, 1, stdin, logfile);
1577 if (freopen("/dev/tty", "r", stdin) == NULL)
1579 errormsg("could not open terminal for input\n");
1583 deletefiles(files, 1, stdin, logfile);
1590 if (ISFLAG(flags, F_SUMMARIZEMATCHES))
1591 summarizematches(files);
1595 printmatches(files);
1598 curfile = files->next;
1599 free(files->d_name);
1600 free(files->crcsignature);
1601 free(files->crcpartial);
1606 for (x = 0; x < argc; x++)
1611 purgetree(checktree);