Parcourir la source

cmake: tar: Allow selective extracting and listing of archives

Bartosz Kosiorek il y a 6 ans
Parent
commit
c8e217e0a7

+ 9 - 3
Help/manual/cmake.1.rst

@@ -528,16 +528,22 @@ Available commands are:
 ``sleep <number>...``
   Sleep for given number of seconds.
 
-``tar [cxt][vf][zjJ] file.tar [<options>] [--] [<file>...]``
+``tar [cxt][vf][zjJ] file.tar [<options>] [--] [<pathname>...]``
   Create or extract a tar or zip archive.  Options are:
 
   ``c``
     Create a new archive containing the specified files.
-    If used, the <file> argument is mandatory.
+    If used, the ``<pathname>...`` argument is mandatory.
   ``x``
     Extract to disk from the archive.
+    The ``<pathname>...`` 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 ``<pathname>...`` argument could be used to list only selected files
+    or directories.
   ``v``
     Produce verbose output.
   ``z``

+ 8 - 0
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``)

+ 54 - 5
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<std::string>& files, bool verbose,
+                 bool extract)
 {
   cmLocaleRAII localeRAII;
   static_cast<void>(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<std::string>& 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<std::string>& 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

+ 4 - 2
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<std::string>& files, bool verbose);
   static bool CreateTar(const char* outFileName,
                         const std::vector<std::string>& 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<std::string>& 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

+ 2 - 2
Source/cmcmd.cxx

@@ -1127,7 +1127,7 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> 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<std::string> 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;
         }

+ 3 - 0
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)

+ 13 - 1
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}

+ 28 - 0
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)