diff --git a/Makefile.am b/Makefile.am index 4c16d94..f58fb4a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,11 +1,11 @@ bin_PROGRAMS = fdupes if NO_NCURSES -fdupes_SOURCES = fdupes.c errormsg.c md5/md5.c fdupes.h errormsg.h md5/md5.h +fdupes_SOURCES = fdupes.c errormsg.c dir.c log.c fmatch.c sigint.c md5/md5.c fdupes.h errormsg.h dir.h log.h fmatch.h sigint.h md5/md5.h notrans_dist_man1_MANS = fdupes.1 else -fdupes_SOURCES = fdupes.c ncurses-commands.c ncurses-getcommand.c ncurses-interface.c ncurses-print.c ncurses-prompt.c ncurses-status.c commandidentifier.c errormsg.c wcs.c md5/md5.c fdupes.h ncurses-commands.h ncurses-getcommand.h ncurses-interface.h ncurses-print.h ncurses-prompt.h ncurses-status.h commandidentifier.h errormsg.h wcs.h filegroup.h md5/md5.h +fdupes_SOURCES = fdupes.c ncurses-commands.c ncurses-getcommand.c ncurses-interface.c ncurses-print.c ncurses-prompt.c ncurses-status.c commandidentifier.c errormsg.c wcs.c dir.c log.c fmatch.c sigint.c md5/md5.c fdupes.h ncurses-commands.h ncurses-getcommand.h ncurses-interface.h ncurses-print.h ncurses-prompt.h ncurses-status.h commandidentifier.h errormsg.h wcs.h filegroup.h dir.h log.h fmatch.h sigint.h md5/md5.h dist_man1_MANS = fdupes.1 fdupes-help.1 endif diff --git a/README b/README index 69181c6..108e5c7 100644 --- a/README +++ b/README @@ -43,6 +43,7 @@ Usage: fdupes [options] DIRECTORY... modification time (BY='time'; default), status change time (BY='ctime'), or filename (BY='name') -i --reverse reverse order while sorting + -l --log=LOGFILE log file deletion choices to LOGFILE -v --version display fdupes version -h --help display this help message diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..11b9a0b --- /dev/null +++ b/dir.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include "dir.h" + +char *getworkingdirectory() +{ + size_t size; + char *result; + char *new_result; + char *cwd; + + size = 1024; + + result = 0; + do + { + new_result = (char*) realloc(result, size); + if (new_result == 0) + { + if (result != 0) + free(result); + + return 0; + } + + result = new_result; + + cwd = getcwd(result, size); + + size *= 2; + } while (cwd == 0 && errno == ERANGE); + + if (cwd == 0) + { + free(result); + return 0; + } + + return result; +} diff --git a/dir.h b/dir.h new file mode 100644 index 0000000..e61be9e --- /dev/null +++ b/dir.h @@ -0,0 +1,6 @@ +#ifndef DIR_H +#define DIR_H + +char *getworkingdirectory(); + +#endif \ No newline at end of file diff --git a/fdupes.1 b/fdupes.1 index 946625a..2fc5358 100644 --- a/fdupes.1 +++ b/fdupes.1 @@ -81,6 +81,9 @@ time - sort by modification time, ctime - sort by status change time, name - sor .B -i --reverse reverse order while sorting .TP +.B -l --log\fR=\fILOGFILE\fR +log file deletion choices to LOGFILE +.TP .B -v --version display fdupes version .TP diff --git a/fdupes.c b/fdupes.c index 062b843..e4a2d96 100644 --- a/fdupes.c +++ b/fdupes.c @@ -41,6 +41,8 @@ #include "fdupes.h" #include "errormsg.h" #include "ncurses-interface.h" +#include "log.h" +#include "sigint.h" #define ISFLAG(a,b) ((a & b) == b) #define SETFLAG(a,b) (a |= b) @@ -762,7 +764,7 @@ int relink(char *oldfile, char *newfile) return 1; } -void deletefiles(file_t *files, int prompt, FILE *tty) +void deletefiles(file_t *files, int prompt, FILE *tty, char *logfile) { int counter; int groups = 0; @@ -779,6 +781,8 @@ void deletefiles(file_t *files, int prompt, FILE *tty) int max = 0; int x; int i; + struct log_info *loginfo; + int log_error; curfile = files; @@ -810,6 +814,12 @@ void deletefiles(file_t *files, int prompt, FILE *tty) exit(1); } + loginfo = 0; + if (logfile != 0) + loginfo = log_open(logfile, &log_error); + + register_sigint_handler(); + while (files) { if (files->hasdupes) { curgroup++; @@ -845,7 +855,24 @@ void deletefiles(file_t *files, int prompt, FILE *tty) fflush(stdout); if (!fgets(preservestr, INPUT_SIZE, tty)) + { preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */ + preservestr[1] = '\0'; + + if (got_sigint) + { + if (loginfo) + log_close(loginfo); + + free(dupelist); + free(preserve); + free(preservestr); + + printf("\n"); + + exit(0); + } + } i = strlen(preservestr) - 1; @@ -861,6 +888,7 @@ void deletefiles(file_t *files, int prompt, FILE *tty) if (!fgets(preservestr + i + 1, INPUT_SIZE, tty)) { preservestr[0] = '\n'; /* treat fgets() failure as if nothing was entered */ + preservestr[1] = '\0'; break; } i = strlen(preservestr)-1; @@ -886,24 +914,44 @@ void deletefiles(file_t *files, int prompt, FILE *tty) printf("\n"); + if (loginfo) + log_begin_set(loginfo); + for (x = 1; x <= counter; x++) { if (preserve[x]) + { printf(" [+] %s\n", dupelist[x]->d_name); + + if (loginfo) + log_file_remaining(loginfo, dupelist[x]->d_name); + } else { if (remove(dupelist[x]->d_name) == 0) { printf(" [-] %s\n", dupelist[x]->d_name); + + if (loginfo) + log_file_deleted(loginfo, dupelist[x]->d_name); } else { printf(" [!] %s ", dupelist[x]->d_name); printf("-- unable to delete file!\n"); + + if (loginfo) + log_file_remaining(loginfo, dupelist[x]->d_name); } } } printf("\n"); + + if (loginfo) + log_end_set(loginfo); } files = files->next; } + if (loginfo) + log_close(loginfo); + free(dupelist); free(preserve); free(preservestr); @@ -979,7 +1027,7 @@ void registerpair(file_t **matchlist, file_t *newmatch, } void deletesuccessor(file_t **existing, file_t *duplicate, - int (*comparef)(file_t *f1, file_t *f2)) + int (*comparef)(file_t *f1, file_t *f2), struct log_info *loginfo) { file_t *to_keep; file_t *to_delete; @@ -1000,11 +1048,21 @@ void deletesuccessor(file_t **existing, file_t *duplicate, if (!ISFLAG(flags, F_HIDEPROGRESS)) fprintf(stderr, "\r%40s\r", " "); printf(" [+] %s\n", to_keep->d_name); + + if (loginfo) + log_file_remaining(loginfo, to_keep->d_name); + if (remove(to_delete->d_name) == 0) { printf(" [-] %s\n", to_delete->d_name); + + if (loginfo) + log_file_deleted(loginfo, to_delete->d_name); } else { printf(" [!] %s ", to_delete->d_name); printf("-- unable to delete file!\n"); + + if (loginfo) + log_file_remaining(loginfo, to_delete->d_name); } printf("\n"); @@ -1052,6 +1110,7 @@ void help_text() printf(" \tmodification time (BY='time'; default), status\n"); printf(" \tchange time (BY='ctime'), or filename (BY='name')\n"); printf(" -i --reverse \treverse order while sorting\n"); + printf(" -l --log=LOGFILE \tlog file deletion choices to LOGFILE\n"); printf(" -v --version \tdisplay fdupes version\n"); printf(" -h --help \tdisplay this help message\n\n"); #ifndef HAVE_GETOPT_H @@ -1073,6 +1132,8 @@ int main(int argc, char **argv) { char **oldargv; int firstrecurse; char *logfile = 0; + struct log_info *loginfo; + int log_error; #ifdef HAVE_GETOPT_H static struct option long_options[] = @@ -1101,6 +1162,7 @@ int main(int argc, char **argv) { { "permissions", 0, 0, 'p' }, { "order", 1, 0, 'o' }, { "reverse", 0, 0, 'i' }, + { "log", 1, 0, 'l' }, { 0, 0, 0, 0 } }; #define GETOPT getopt_long @@ -1114,7 +1176,7 @@ int main(int argc, char **argv) { oldargv = cloneargs(argc, argv); - while ((opt = GETOPT(argc, argv, "frRq1SsHlnAdPvhNImpo:i" + while ((opt = GETOPT(argc, argv, "frRq1SsHnAdPvhNImpo:il:" #ifdef HAVE_GETOPT_H , long_options, NULL #endif @@ -1189,6 +1251,19 @@ int main(int argc, char **argv) { case 'i': SETFLAG(flags, F_REVERSE); break; + case 'l': + loginfo = log_open(logfile=optarg, &log_error); + if (loginfo == 0) + { + if (log_error == LOG_ERROR_NOT_A_LOG_FILE) + errormsg("%s: doesn't look like an fdupes log file\n", logfile); + else + errormsg("%s: could not open log file\n", logfile); + + exit(1); + } + log_close(loginfo); + break; default: fprintf(stderr, "Try `fdupes --help' for more information.\n"); @@ -1267,7 +1342,7 @@ int main(int argc, char **argv) { if (ISFLAG(flags, F_DELETEFILES) && ISFLAG(flags, F_IMMEDIATE)) deletesuccessor(match, curfile, (ordertype == ORDER_MTIME || - ordertype == ORDER_CTIME) ? sort_pairs_by_time : sort_pairs_by_filename ); + ordertype == ORDER_CTIME) ? sort_pairs_by_time : sort_pairs_by_filename, loginfo ); else registerpair(match, curfile, (ordertype == ORDER_MTIME || @@ -1293,7 +1368,7 @@ int main(int argc, char **argv) { { if (ISFLAG(flags, F_NOPROMPT)) { - deletefiles(files, 0, 0); + deletefiles(files, 0, 0, logfile); } else { @@ -1302,7 +1377,7 @@ int main(int argc, char **argv) { { if (newterm(getenv("TERM"), stdout, stdin) != 0) { - deletefiles_ncurses(files); + deletefiles_ncurses(files, logfile); } else { @@ -1320,7 +1395,7 @@ int main(int argc, char **argv) { exit(1); } - deletefiles(files, 1, stdin); + deletefiles(files, 1, stdin, logfile); } #else stdin = freopen("/dev/tty", "r", stdin); @@ -1330,7 +1405,7 @@ int main(int argc, char **argv) { exit(1); } - deletefiles(files, 1, stdin); + deletefiles(files, 1, stdin, logfile); #endif } } diff --git a/fmatch.c b/fmatch.c new file mode 100644 index 0000000..9cd1ac8 --- /dev/null +++ b/fmatch.c @@ -0,0 +1,29 @@ +#include +#include "fmatch.h" + +/* Test whether given string matches text at current file position. +*/ +void fmatch(FILE *file, char *matchstring, int *is_match, size_t *chars_read) +{ + size_t len; + int x; + int c; + + *is_match = 0; + *chars_read = 0; + + len = strlen(matchstring); + for (x = 0; x < len; ++x) + { + c = fgetc(file); + if (c == EOF) + return; + + (*chars_read)++; + + if ((char)c != matchstring[x]) + return; + } + + *is_match = 1; +} diff --git a/fmatch.h b/fmatch.h new file mode 100644 index 0000000..130054f --- /dev/null +++ b/fmatch.h @@ -0,0 +1,8 @@ +#ifndef FMATCH_H +#define FMATCH_H + +#include + +void fmatch(FILE *file, char *matchstring, int *is_match, size_t *chars_read); + +#endif diff --git a/log.c b/log.c new file mode 100644 index 0000000..3fa453b --- /dev/null +++ b/log.c @@ -0,0 +1,211 @@ +#include +#include +#include +#include "fmatch.h" +#include "dir.h" +#include "log.h" + +#define LOG_HEADER "[fdupes log]\n" + +/* Open log file in append mode. If file exists, make sure it is a valid fdupes log file. +*/ +struct log_info *log_open(char *filename, int *error) +{ + struct log_info *info; + int is_match; + size_t read; + + info = (struct log_info*) malloc(sizeof(struct log_info)); + if (info == 0) + { + if (error != 0) + *error = LOG_ERROR_OUT_OF_MEMORY; + + return 0; + } + + info->file = fopen(filename, "a+"); + if (info->file == 0) + { + if (error != 0) + *error = LOG_ERROR_FOPEN_FAILED; + + free(info); + return 0; + } + + fmatch(info->file, LOG_HEADER, &is_match, &read); + if (!is_match && read > 0) + { + if (error != 0) + *error = LOG_ERROR_NOT_A_LOG_FILE; + + free(info); + return 0; + } + + info->append = read > 0; + + info->log_start = 1; + info->deleted = 0; + info->remaining = 0; + + if (error != 0) + *error = LOG_ERROR_NONE; + + return info; +} + +/* Free linked lists holding set of deleted and remaining files. +*/ +void log_free_set(struct log_info *info) +{ + struct log_file *f; + struct log_file *next; + + f = info->deleted; + while (f != 0) + { + next = f->next; + + free(f); + + f = next; + } + + f = info->remaining; + while (f != 0) + { + next = f->next; + + free(f); + + f = next; + } + + info->deleted = 0; + info->remaining = 0; +} + +/* Signal beginning of duplicate set. +*/ +void log_begin_set(struct log_info *info) +{ + log_free_set(info); +} + +/* Add deleted file to log. +*/ +int log_file_deleted(struct log_info *info, char *name) +{ + struct log_file *file; + + file = (struct log_file*) malloc(sizeof(struct log_file)); + if (file == 0) + return 0; + + file->next = info->deleted; + file->filename = name; + + info->deleted = file; + + return 1; +} + +/* Add remaining file to log. +*/ +int log_file_remaining(struct log_info *info, char *name) +{ + struct log_file *file; + + file = (struct log_file*) malloc(sizeof(struct log_file)); + if (file == 0) + return 0; + + file->next = info->remaining; + file->filename = name; + + info->remaining = file; + + return 1; +} + +/* Output log header. +*/ +void log_header(FILE *file) +{ + fprintf(file, "%s\n", LOG_HEADER); +} + +/* Output log timestamp. +*/ +void log_timestamp(FILE *file) +{ + time_t t = time(NULL); + struct tm tm = *localtime(&t); + + fprintf(file, "Log entry for %d-%02d-%02d %02d:%02d:%02d\n\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +/* Output current working directory. +*/ +void log_cwd(FILE *file) +{ + char *cwd = getworkingdirectory(); + + fprintf(file, "working directory:\n %s\n\n", cwd); + + free(cwd); +} + +/* Signal the end of a duplicate set. +*/ +void log_end_set(struct log_info *info) +{ + struct log_file *f; + + if (info->deleted == 0) + return; + + if (info->log_start) + { + if (info->append) + fprintf(info->file, "---\n\n"); + else + log_header(info->file); + + log_timestamp(info->file); + log_cwd(info->file); + + info->log_start = 0; + } + + f = info->deleted; + do + { + fprintf(info->file, "deleted %s\n", f->filename); + f = f->next; + } while (f != 0); + + f = info->remaining; + while (f != 0) + { + fprintf(info->file, " left %s\n", f->filename); + f = f->next; + } + + fprintf(info->file, "\n"); + + fflush(info->file); +} + +/* Close log and free all memory. +*/ +void log_close(struct log_info *info) +{ + fclose(info->file); + + log_free_set(info); + + free(info); +} diff --git a/log.h b/log.h new file mode 100644 index 0000000..9e21d87 --- /dev/null +++ b/log.h @@ -0,0 +1,33 @@ +#ifndef LOG_H +#define LOG_H + +#include + +#define LOG_ERROR_NONE 0 +#define LOG_ERROR_OUT_OF_MEMORY 1 +#define LOG_ERROR_FOPEN_FAILED 2 +#define LOG_ERROR_NOT_A_LOG_FILE 3 + +struct log_file +{ + char *filename; + struct log_file *next; +}; + +struct log_info +{ + FILE *file; + int append; + int log_start; + struct log_file *deleted; + struct log_file *remaining; +}; + +struct log_info *log_open(char *filename, int *error); +void log_begin_set(struct log_info *info); +int log_file_deleted(struct log_info *info, char *name); +int log_file_remaining(struct log_info *info, char *name); +void log_end_set(struct log_info *info); +void log_close(struct log_info *info); + +#endif \ No newline at end of file diff --git a/ncurses-interface.c b/ncurses-interface.c index a1d24fd..c8b683f 100644 --- a/ncurses-interface.c +++ b/ncurses-interface.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #ifdef HAVE_NCURSESW_CURSES_H #include @@ -17,13 +16,8 @@ #include "commandidentifier.h" #include "filegroup.h" #include "errormsg.h" - -volatile sig_atomic_t got_sigint = 0; - -void sigint_handler(int signal) -{ - got_sigint = 1; -} +#include "log.h" +#include "sigint.h" enum linestyle { @@ -323,7 +317,7 @@ int validate_file_list(struct filegroup *currentgroup, wchar_t *commandbuffer_in return FILE_LIST_OK; } -void deletefiles_ncurses(file_t *files) +void deletefiles_ncurses(file_t *files, char *logfile) { WINDOW *filewin; WINDOW *promptwin; @@ -372,11 +366,11 @@ void deletefiles_ncurses(file_t *files) struct prompt_info *prompt; int dupesfound; int intresult; - struct sigaction action; int adjusttopline; int toplineoffset = 0; int resumecommandinput = 0; int index_width; + struct log_info *loginfo; noecho(); cbreak(); @@ -437,8 +431,7 @@ void deletefiles_ncurses(file_t *files) exit(1); } - action.sa_handler = sigint_handler; - sigaction(SIGINT, &action, 0); + register_sigint_handler(); curfile = files; while (curfile) @@ -1195,6 +1188,11 @@ void deletefiles_ncurses(file_t *files) totaldeleted = 0; deletedbytes = 0; + if (logfile != 0) + loginfo = log_open(logfile, 0); + else + loginfo = 0; + for (g = 0; g < totalgroups; ++g) { preservecount = 0; @@ -1217,6 +1215,9 @@ void deletefiles_ncurses(file_t *files) } } + if (loginfo) + log_begin_set(loginfo); + /* delete files marked for deletion unless no files left undeleted */ if (deletecount < groups[g].filecount) { @@ -1230,13 +1231,28 @@ void deletefiles_ncurses(file_t *files) deletedbytes += groups[g].files[f].file->size; ++totaldeleted; + + if (loginfo) + log_file_deleted(loginfo, groups[g].files[f].file->d_name); } } } + if (loginfo) + { + for (f = 0; f < groups[g].filecount; ++f) + { + if (groups[g].files[f].action >= 0) + log_file_remaining(loginfo, groups[g].files[f].file->d_name); + } + } + deletecount = 0; } + if (loginfo) + log_end_set(loginfo); + /* if no files left unresolved, mark preserved files for delisting */ if (unresolvedcount == 0) { @@ -1267,6 +1283,9 @@ void deletefiles_ncurses(file_t *files) cursorfile = groups[g].filecount - 1; } + if (loginfo != 0) + log_close(loginfo); + if (deletedbytes < 1000.0) format_status_left(status, L"Deleted %ld files (occupying %.0f bytes).", totaldeleted, deletedbytes); else if (deletedbytes <= (1000.0 * 1000.0)) diff --git a/ncurses-interface.h b/ncurses-interface.h index 7a3780f..b82fab8 100644 --- a/ncurses-interface.h +++ b/ncurses-interface.h @@ -3,6 +3,6 @@ #include "fdupes.h" -void deletefiles_ncurses(file_t *files); +void deletefiles_ncurses(file_t *files, char *logfile); #endif \ No newline at end of file diff --git a/sigint.c b/sigint.c new file mode 100644 index 0000000..05309a7 --- /dev/null +++ b/sigint.c @@ -0,0 +1,19 @@ +#include +#include "sigint.h" + +volatile sig_atomic_t got_sigint = 0; + +void sigint_handler(int signal) +{ + got_sigint = 1; +} + +void register_sigint_handler() +{ + struct sigaction action; + + memset(&action, 0, sizeof(struct sigaction)); + + action.sa_handler = sigint_handler; + sigaction(SIGINT, &action, 0); +} \ No newline at end of file diff --git a/sigint.h b/sigint.h new file mode 100644 index 0000000..a34d1f8 --- /dev/null +++ b/sigint.h @@ -0,0 +1,10 @@ +#ifndef SIGINT_H +#define SIGINT_H + +#include + +extern volatile sig_atomic_t got_sigint; + +void register_sigint_handler(); + +#endif \ No newline at end of file