From c8e217e0a70f8f445e2c593f44e1105c959fb9d7 Mon Sep 17 00:00:00 2001 From: Bartosz Kosiorek Date: Wed, 22 May 2019 21:29:08 +0200 Subject: cmake: tar: Allow selective extracting and listing of archives --- Help/manual/cmake.1.rst | 12 +++-- .../dev/cmake-e-tar-extract-specific-files.rst | 8 +++ Source/cmSystemTools.cxx | 59 ++++++++++++++++++++-- Source/cmSystemTools.h | 6 ++- Source/cmcmd.cxx | 4 +- Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake | 3 ++ Tests/RunCMake/CommandLineTar/roundtrip.cmake | 14 ++++- Tests/RunCMake/CommandLineTar/zip-filtered.cmake | 28 ++++++++++ 8 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 Help/release/dev/cmake-e-tar-extract-specific-files.rst create mode 100644 Tests/RunCMake/CommandLineTar/zip-filtered.cmake diff --git a/Help/manual/cmake.1.rst b/Help/manual/cmake.1.rst index df0d4c5..97c65e7 100644 --- a/Help/manual/cmake.1.rst +++ b/Help/manual/cmake.1.rst @@ -528,16 +528,22 @@ Available commands are: ``sleep ...`` Sleep for given number of seconds. -``tar [cxt][vf][zjJ] file.tar [] [--] [...]`` +``tar [cxt][vf][zjJ] file.tar [] [--] [...]`` Create or extract a tar or zip archive. Options are: ``c`` Create a new archive containing the specified files. - If used, the argument is mandatory. + If used, the ``...`` argument is mandatory. ``x`` Extract to disk from the archive. + The ``...`` argument could be used to extract only selected files + or directories. + When extracting selected files or directories, you must provide their exact + names including the path, as printed by list (``-t``). ``t`` - List archive contents to stdout. + List archive contents. + The ``...`` argument could be used to list only selected files + or directories. ``v`` Produce verbose output. ``z`` diff --git a/Help/release/dev/cmake-e-tar-extract-specific-files.rst b/Help/release/dev/cmake-e-tar-extract-specific-files.rst new file mode 100644 index 0000000..4ab3eab --- /dev/null +++ b/Help/release/dev/cmake-e-tar-extract-specific-files.rst @@ -0,0 +1,8 @@ +cmake-e-tar-extract-specific-files +---------------------------------- + +* The :manual:`cmake(1)` ``-E tar`` tool allow for extract (``-x``) or list + (``-t``) only specific files or directories. To select pathnames append + a space-separated list of file names or directories. + When extracting selected files or directories, you must provide their exact + pathname, as printed by list (``-t``) diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 6359d60..d00d4d0 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1757,7 +1757,9 @@ bool copy_data(struct archive* ar, struct archive* aw) # endif } -bool extract_tar(const char* outFileName, bool verbose, bool extract) +bool extract_tar(const char* outFileName, + const std::vector& files, bool verbose, + bool extract) { cmLocaleRAII localeRAII; static_cast(localeRAII); @@ -1766,6 +1768,21 @@ bool extract_tar(const char* outFileName, bool verbose, bool extract) archive_read_support_filter_all(a); archive_read_support_format_all(a); struct archive_entry* entry; + + struct archive* matching = archive_match_new(); + if (matching == nullptr) { + cmSystemTools::Error("Out of memory"); + return false; + } + + for (const auto& filename : files) { + if (archive_match_include_pattern(matching, filename.c_str()) != + ARCHIVE_OK) { + cmSystemTools::Error("Failed to add to inclusion list: " + filename); + return false; + } + } + int r = cm_archive_read_open_file(a, outFileName, 10240); if (r) { ArchiveError("Problem with archive_read_open_file(): ", a); @@ -1782,6 +1799,11 @@ bool extract_tar(const char* outFileName, bool verbose, bool extract) ArchiveError("Problem with archive_read_next_header(): ", a); break; } + + if (archive_match_excluded(matching, entry)) { + continue; + } + if (verbose) { if (extract) { cmSystemTools::Stdout("x "); @@ -1827,6 +1849,27 @@ bool extract_tar(const char* outFileName, bool verbose, bool extract) } } } + + bool error_occured = false; + if (matching != nullptr) { + const char* p; + int ar; + + while ((ar = archive_match_path_unmatched_inclusions_next(matching, &p)) == + ARCHIVE_OK) { + cmSystemTools::Error("tar: " + std::string(p) + + ": Not found in archive"); + error_occured = true; + } + if (error_occured) { + return false; + } + if (ar == ARCHIVE_FATAL) { + cmSystemTools::Error("tar: Out of memory"); + return false; + } + } + archive_match_free(matching); archive_write_free(ext); archive_read_close(a); archive_read_free(a); @@ -1835,23 +1878,29 @@ bool extract_tar(const char* outFileName, bool verbose, bool extract) } #endif -bool cmSystemTools::ExtractTar(const char* outFileName, bool verbose) +bool cmSystemTools::ExtractTar(const char* outFileName, + const std::vector& files, + bool verbose) { #if defined(CMAKE_BUILD_WITH_CMAKE) - return extract_tar(outFileName, verbose, true); + return extract_tar(outFileName, files, verbose, true); #else (void)outFileName; + (void)files; (void)verbose; return false; #endif } -bool cmSystemTools::ListTar(const char* outFileName, bool verbose) +bool cmSystemTools::ListTar(const char* outFileName, + const std::vector& files, + bool verbose) { #if defined(CMAKE_BUILD_WITH_CMAKE) - return extract_tar(outFileName, verbose, false); + return extract_tar(outFileName, files, verbose, false); #else (void)outFileName; + (void)files; (void)verbose; return false; #endif diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index 80c6ee3..09a4d13 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -450,13 +450,15 @@ public: TarCompressNone }; - static bool ListTar(const char* outFileName, bool verbose); + static bool ListTar(const char* outFileName, + const std::vector& files, bool verbose); static bool CreateTar(const char* outFileName, const std::vector& files, cmTarCompression compressType, bool verbose, std::string const& mtime = std::string(), std::string const& format = std::string()); - static bool ExtractTar(const char* inFileName, bool verbose); + static bool ExtractTar(const char* inFileName, + const std::vector& files, bool verbose); // This should be called first thing in main // it will keep child processes from inheriting the // stdin and stdout of this process. This is important diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx index 3c75957..bcf7b2f 100644 --- a/Source/cmcmd.cxx +++ b/Source/cmcmd.cxx @@ -1127,7 +1127,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args) return 1; } if (action == cmSystemTools::TarActionList) { - if (!cmSystemTools::ListTar(outFile.c_str(), verbose)) { + if (!cmSystemTools::ListTar(outFile.c_str(), files, verbose)) { cmSystemTools::Error("Problem listing tar: " + outFile); return 1; } @@ -1142,7 +1142,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector const& args) return 1; } } else if (action == cmSystemTools::TarActionExtract) { - if (!cmSystemTools::ExtractTar(outFile.c_str(), verbose)) { + if (!cmSystemTools::ExtractTar(outFile.c_str(), files, verbose)) { cmSystemTools::Error("Problem extracting tar: " + outFile); return 1; } diff --git a/Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake b/Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake index 5deb110..4d1b396 100644 --- a/Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake +++ b/Tests/RunCMake/CommandLineTar/RunCMakeTest.cmake @@ -30,3 +30,6 @@ run_cmake(pax-xz) run_cmake(paxr) run_cmake(paxr-bz2) run_cmake(zip) + +# Extracting only selected files or directories +run_cmake(zip-filtered) diff --git a/Tests/RunCMake/CommandLineTar/roundtrip.cmake b/Tests/RunCMake/CommandLineTar/roundtrip.cmake index dc1c885..fa63d12 100644 --- a/Tests/RunCMake/CommandLineTar/roundtrip.cmake +++ b/Tests/RunCMake/CommandLineTar/roundtrip.cmake @@ -47,7 +47,11 @@ file(REMOVE_RECURSE ${FULL_DECOMPRESS_DIR}) file(MAKE_DIRECTORY ${FULL_DECOMPRESS_DIR}) run_tar(${CMAKE_CURRENT_BINARY_DIR} ${COMPRESSION_FLAGS} ${FULL_OUTPUT_NAME} ${COMPRESSION_OPTIONS} ${COMPRESS_DIR}) -run_tar(${FULL_DECOMPRESS_DIR} ${DECOMPRESSION_FLAGS} ${FULL_OUTPUT_NAME} ${DECOMPRESSION_OPTIONS}) +run_tar(${FULL_DECOMPRESS_DIR} ${DECOMPRESSION_FLAGS} ${FULL_OUTPUT_NAME} ${DECOMPRESSION_OPTIONS} -- ${DECOMPRESSION_PATHNAMES}) + +if(CUSTOM_CHECK_FILES) + set(CHECK_FILES ${CUSTOM_CHECK_FILES}) +endif() foreach(file ${CHECK_FILES}) set(input ${FULL_COMPRESS_DIR}/${file}) @@ -69,6 +73,14 @@ foreach(file ${CHECK_FILES}) endif() endforeach() +foreach(file ${NOT_EXISTING_FILES_CHECK}) + set(output ${FULL_DECOMPRESS_DIR}/${COMPRESS_DIR}/${file}) + + if(EXISTS ${output}) + message(SEND_ERROR "File ${output} exists but it shouldn't") + endif() +endforeach() + function(check_magic EXPECTED) file(READ ${FULL_OUTPUT_NAME} ACTUAL ${ARGN} diff --git a/Tests/RunCMake/CommandLineTar/zip-filtered.cmake b/Tests/RunCMake/CommandLineTar/zip-filtered.cmake new file mode 100644 index 0000000..c13210e --- /dev/null +++ b/Tests/RunCMake/CommandLineTar/zip-filtered.cmake @@ -0,0 +1,28 @@ +set(OUTPUT_NAME "test.zip") + +set(COMPRESSION_FLAGS cvf) +set(COMPRESSION_OPTIONS --format=zip) + +set(DECOMPRESSION_FLAGS xvf) +set(LIST_ARCHIVE TRUE) +set(DECOMPRESSION_PATHNAMES + compress_dir/f1.txt # Decompress only file + compress_dir/d1 # and whole directory +) + +set(CUSTOM_CHECK_FILES + "f1.txt" + "d1/f1.txt" +) + +# This files shouldn't exists +set(NOT_EXISTING_FILES_CHECK + "d 2/f1.txt" + "d + 3/f1.txt" + "d_4/f1.txt" + "d-4/f1.txt" +) + +include(${CMAKE_CURRENT_LIST_DIR}/roundtrip.cmake) + +check_magic("504b0304" LIMIT 4 HEX) -- cgit v0.12