diff --git a/programs/fileio.c b/programs/fileio.c index 72f716f38..241afd7cf 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -619,17 +619,10 @@ FIO_openDstFile(FIO_prefs_t* const prefs, dstFileName); return NULL; } - DISPLAY("zstd: %s already exists; overwrite (y/N) ? ", - dstFileName); - { int ch = getchar(); - if ((ch!='Y') && (ch!='y')) { - DISPLAY(" not overwritten \n"); - /* flush rest of input line */ - while ((ch!=EOF) && (ch!='\n')) ch = getchar(); - return NULL; - } - while ((ch!=EOF) && (ch!='\n')) ch = getchar(); - } } + DISPLAY("zstd: %s already exists; ", dstFileName); + if (UTIL_requireUserConfirmation("overwrite (y/n) ? ", "Not overwritten \n", "yY")) + return NULL; + } /* need to unlink */ FIO_removeFile(dstFileName); } } @@ -796,6 +789,42 @@ static void FIO_adjustMemLimitForPatchFromMode(FIO_prefs_t* const prefs, FIO_setMemLimit(prefs, (unsigned)maxSize); } +/* FIO_removeMultiFilesWarning() : + * Returns 1 if the console should abort, 0 if console should proceed. + * This function handles logic when processing multiple files with -o, displaying the appropriate warnings/prompts. + * + * If -f is specified, or there is just 1 file, zstd will always proceed as usual. + * If --rm is specified, there will be a prompt asking for user confirmation. + * If -f is specified with --rm, zstd will proceed as usual + * If -q is specified with --rm, zstd will abort pre-emptively + * If neither flag is specified, zstd will prompt the user for confirmation to proceed. + * If --rm is not specified, then zstd will print a warning to the user (which can be silenced with -q). + */ +static int FIO_removeMultiFilesWarning(const FIO_prefs_t* const prefs, int displayLevelCutoff, const char* outFileName) +{ + int error = 0; + if (prefs->nbFiles > 1 && !prefs->overwrite) { + if (g_display_prefs.displayLevel <= displayLevelCutoff) { + if (prefs->removeSrcFile) { + DISPLAYLEVEL(1, "zstd: Aborting... not deleting files and processing into dst: %s", outFileName); + error = 1; + } + } else { + if (!strcmp(outFileName, stdoutmark)) { + DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into stdout. "); + } else { + DISPLAYLEVEL(2, "zstd: WARNING: all input files will be processed and concatenated into a single output file: %s ", outFileName); + } + DISPLAYLEVEL(2, "\nThe concatenated output CANNOT regenerate the original directory tree. ") + if (prefs->removeSrcFile) { + error = g_display_prefs.displayLevel > displayLevelCutoff && UTIL_requireUserConfirmation("This is a destructive operation. Proceed? (y/n): ", "Aborting...", "yY"); + } + } + DISPLAY("\n"); + } + return error; +} + #ifndef ZSTD_NOCOMPRESS /* ********************************************************************** @@ -1700,6 +1729,10 @@ int FIO_compressMultipleFilenames(FIO_prefs_t* const prefs, /* init */ assert(outFileName != NULL || suffix != NULL); if (outFileName != NULL) { /* output into a single destination (stdout typically) */ + if (FIO_removeMultiFilesWarning(prefs, 1 /* displayLevelCutoff */, outFileName)) { + FIO_freeCResources(ress); + return 1; + } ress.dstFile = FIO_openDstFile(prefs, NULL, outFileName); if (ress.dstFile == NULL) { /* could not open outFileName */ error = 1; @@ -2594,6 +2627,10 @@ FIO_decompressMultipleFilenames(FIO_prefs_t* const prefs, dRess_t ress = FIO_createDResources(prefs, dictFileName); if (outFileName) { + if (FIO_removeMultiFilesWarning(prefs, 1 /* displayLevelCutoff */, outFileName)) { + FIO_freeDResources(ress); + return 1; + } if (!prefs->testMode) { ress.dstFile = FIO_openDstFile(prefs, NULL, outFileName); if (ress.dstFile == 0) EXM_THROW(19, "cannot open %s", outFileName); diff --git a/programs/util.c b/programs/util.c index fc88ab35c..d828dc428 100644 --- a/programs/util.c +++ b/programs/util.c @@ -87,6 +87,22 @@ UTIL_STATIC void* UTIL_realloc(void *ptr, size_t size) ******************************************/ int g_utilDisplayLevel; +int UTIL_requireUserConfirmation(const char* prompt, const char* abortMsg, + const char* acceptableLetters) { + int ch, result; + UTIL_DISPLAY("%s", prompt); + ch = getchar(); + result = 0; + if (strchr(acceptableLetters, ch) == NULL) { + UTIL_DISPLAY("%s", abortMsg); + result = 1; + } + /* flush the rest */ + while ((ch!=EOF) && (ch!='\n')) + ch = getchar(); + return result; +} + /*-************************************* * Constants diff --git a/programs/util.h b/programs/util.h index f8eee86ad..eeb6a15e2 100644 --- a/programs/util.h +++ b/programs/util.h @@ -93,6 +93,12 @@ extern "C" { ******************************************/ extern int g_utilDisplayLevel; +/** + * Displays a message prompt and returns success (0) if first character from stdin + * matches any from acceptableLetters. Otherwise, returns failure (1) and displays abortMsg. + */ +int UTIL_requireUserConfirmation(const char* prompt, const char* abortMsg, const char* acceptableLetters); + /*-**************************************** * File functions diff --git a/programs/zstd.1.md b/programs/zstd.1.md index 5b90b2df0..e7fa9cd4e 100644 --- a/programs/zstd.1.md +++ b/programs/zstd.1.md @@ -213,7 +213,8 @@ the last one takes effect. and disabled when output is stdout. This setting overrides default and can force sparse mode over stdout. * `--rm`: - remove source file(s) after successful compression or decompression + remove source file(s) after successful compression or decompression. If used in combination with + -o, will trigger a confirmation prompt (which can be silenced with -f), as this is a destructive operation. * `-k`, `--keep`: keep source file(s) after successful compression or decompression. This is the default behavior. diff --git a/tests/playTests.sh b/tests/playTests.sh index f3b66e349..608d4228f 100755 --- a/tests/playTests.sh +++ b/tests/playTests.sh @@ -369,6 +369,22 @@ zstd -d tmp1.zst tmp2.zst -o tmp touch tmpexists zstd tmp1 tmp2 -f -o tmpexists zstd tmp1 tmp2 -q -o tmpexists && die "should have refused to overwrite" +println gooder > tmp_rm1 +println boi > tmp_rm2 +println worldly > tmp_rm3 +echo 'y' | zstd tmp_rm1 tmp_rm2 -o tmp_rm3.zst --rm # tests the warning prompt for --rm with multiple inputs into once source +test ! -f tmp_rm1 +test ! -f tmp_rm2 +cp tmp_rm3.zst tmp_rm4.zst +echo 'Y' | zstd -d tmp_rm3.zst tmp_rm4.zst -o tmp_rm_out --rm +test ! -f tmp_rm3.zst +test ! -f tmp_rm4.zst +echo 'yes' | zstd tmp_rm_out tmp_rm3 -c --rm +test ! -f tmp_rm_out +test ! -f tmp_rm3 +println gooder > tmpexists1 +zstd tmpexists1 tmpexists -c --rm -f + # Bug: PR #972 if [ "$?" -eq 139 ]; then die "should not have segfaulted"