From 769d4810c8c63ee441fa6d23d2b52e4cba791cc4 Mon Sep 17 00:00:00 2001 From: xiaobai0529 <190944823+xiaobai0529@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:02:38 +0800 Subject: [PATCH] Fix output-dir-flat collisions with --rm --- programs/fileio.c | 44 +++++++++++++++---- programs/fileio.h | 5 ++- tests/cli-tests/basic/output_dir_collision.sh | 40 +++++++++++++++++ .../basic/output_dir_collision.sh.exit | 1 + 4 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 tests/cli-tests/basic/output_dir_collision.sh create mode 100644 tests/cli-tests/basic/output_dir_collision.sh.exit diff --git a/programs/fileio.c b/programs/fileio.c index 78a906d533b..968c18a5982 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1142,11 +1142,13 @@ static void FIO_initDict(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* co /* FIO_checkFilenameCollisions() : - * Checks for and warns if there are any files that would have the same output path + * Checks for files that would have the same output path. + * Returns non-zero if a collision is fatal, i.e. when --rm is enabled. */ -int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) { +int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles, int removeSrcFile) { const char **filenameTableSorted, *prevElem, *filename; unsigned u; + int collision = 0; filenameTableSorted = (const char**) malloc(sizeof(char*) * nbFiles); if (!filenameTableSorted) { @@ -1156,6 +1158,11 @@ int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) { for (u = 0; u < nbFiles; ++u) { filename = strrchr(filenameTable[u], PATH_SEP); +#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) + { const char* const slash = strrchr(filenameTable[u], '/'); + if (slash > filename) filename = slash; + } +#endif if (filename == NULL) { filenameTableSorted[u] = filenameTable[u]; } else { @@ -1166,14 +1173,25 @@ int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) { qsort((void*)filenameTableSorted, nbFiles, sizeof(char*), UTIL_compareStr); prevElem = filenameTableSorted[0]; for (u = 1; u < nbFiles; ++u) { - if (strcmp(prevElem, filenameTableSorted[u]) == 0) { - DISPLAYLEVEL(2, "WARNING: Two files have same filename: %s\n", prevElem); + int const sameName = +#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) + _stricmp(prevElem, filenameTableSorted[u]) == 0; +#else + strcmp(prevElem, filenameTableSorted[u]) == 0; +#endif + if (sameName) { + if (removeSrcFile) { + DISPLAYLEVEL(1, "ERROR: Two files have same filename: %s\n", prevElem); + collision = 1; + } else { + DISPLAYLEVEL(2, "WARNING: Two files have same filename: %s\n", prevElem); + } } prevElem = filenameTableSorted[u]; } free((void*)filenameTableSorted); - return 0; + return collision; } static const char* @@ -2512,6 +2530,12 @@ int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx, if (outMirroredRootDirName) UTIL_mirrorSourceFilesDirectories(inFileNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName); + if (outDirName && FIO_checkFilenameCollisions(inFileNamesTable, (unsigned)fCtx->nbFilesTotal, + prefs->removeSrcFile)) { + FIO_freeCResources(&ress); + return 1; + } + for (; fCtx->currFileIdx < fCtx->nbFilesTotal; ++fCtx->currFileIdx) { const char* const srcFileName = inFileNamesTable[fCtx->currFileIdx]; const char* dstFileName = NULL; @@ -2533,8 +2557,6 @@ int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx, error |= status; } - if (outDirName) - FIO_checkFilenameCollisions(inFileNamesTable , (unsigned)fCtx->nbFilesTotal); } if (FIO_shouldDisplayMultipleFileSummary(fCtx)) { @@ -3367,6 +3389,12 @@ FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx, if (outMirroredRootDirName) UTIL_mirrorSourceFilesDirectories(srcNamesTable, (unsigned)fCtx->nbFilesTotal, outMirroredRootDirName); + if (outDirName && FIO_checkFilenameCollisions(srcNamesTable, (unsigned)fCtx->nbFilesTotal, + prefs->removeSrcFile)) { + FIO_freeDResources(ress); + return 1; + } + for (; fCtx->currFileIdx < fCtx->nbFilesTotal; fCtx->currFileIdx++) { /* create dstFileName */ const char* const srcFileName = srcNamesTable[fCtx->currFileIdx]; const char* dstFileName = NULL; @@ -3386,8 +3414,6 @@ FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx, if (!status) fCtx->nbFilesProcessed++; error |= status; } - if (outDirName) - FIO_checkFilenameCollisions(srcNamesTable , (unsigned)fCtx->nbFilesTotal); } if (FIO_shouldDisplayMultipleFileSummary(fCtx)) { diff --git a/programs/fileio.h b/programs/fileio.h index 5d7334ef5f0..34050ee1cb5 100644 --- a/programs/fileio.h +++ b/programs/fileio.h @@ -151,9 +151,10 @@ int FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx, const char* dictFileName); /* FIO_checkFilenameCollisions() : - * Checks for and warns if there are any files that would have the same output path + * Checks for files that would have the same output path. + * Returns non-zero if a collision is fatal, i.e. when --rm is enabled. */ -int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles); +int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles, int removeSrcFile); diff --git a/tests/cli-tests/basic/output_dir_collision.sh b/tests/cli-tests/basic/output_dir_collision.sh new file mode 100644 index 00000000000..89bcbcb5c31 --- /dev/null +++ b/tests/cli-tests/basic/output_dir_collision.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +set -e + +mkdir d1 d2 out +printf 'first\n' > d1/x +printf 'second\n' > d2/x + +zstd -f --rm --output-dir-flat out d1/x d2/x && die "Collision with --rm should fail!" +test -f d1/x +test -f d2/x +test ! -e out/x.zst + +zstd -f --output-dir-flat out d1/x d2/x +test -f d1/x +test -f d2/x +test -f out/x.zst + +mkdir dz1 dz2 dout +printf 'first\n' | zstd -q > dz1/x.zst +printf 'second\n' | zstd -q > dz2/x.zst + +zstd -d -f --rm --output-dir-flat dout dz1/x.zst dz2/x.zst && die "Decompression collision with --rm should fail!" +test -f dz1/x.zst +test -f dz2/x.zst +test ! -e dout/x + +zstd -d -f --output-dir-flat dout dz1/x.zst dz2/x.zst +test -f dz1/x.zst +test -f dz2/x.zst +test -f dout/x + +if [ "${OS:-}" = "Windows_NT" ]; then + printf 'first case\n' > d1/A + printf 'second case\n' > d2/a + zstd -f --rm --output-dir-flat out d1/A d2/a && die "Case collision with --rm should fail!" + test -f d1/A + test -f d2/a +fi +exit 0 diff --git a/tests/cli-tests/basic/output_dir_collision.sh.exit b/tests/cli-tests/basic/output_dir_collision.sh.exit new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/tests/cli-tests/basic/output_dir_collision.sh.exit @@ -0,0 +1 @@ +0