]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
7zip reader: add support for ARM64 filter (#1918)
authorMostyn Bramley-Moore <mostyn@antipode.se>
Mon, 17 Jul 2023 11:00:23 +0000 (13:00 +0200)
committerGitHub <noreply@github.com>
Mon, 17 Jul 2023 11:00:23 +0000 (13:00 +0200)
7-Zip 23.00 added a new ARM64 filter, which is also supported by recent
versions of liblzma. This PR adds support for this filter for both lzma
and non-lzma encoders.

Makefile.am
configure.ac
libarchive/archive_read_support_format_7zip.c
libarchive/test/test_read_format_7zip.c
libarchive/test/test_read_format_7zip_deflate_arm64.7z.uu [new file with mode: 0644]
libarchive/test/test_read_format_7zip_lzma2_arm64.7z.uu [new file with mode: 0644]

index 4dd038829e04025f69a891b2674783de2105b96c..7f0198a40c46d06451a5d11209f22f7e7c7c7b09 100644 (file)
@@ -768,6 +768,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_7zip_copy.7z.uu \
        libarchive/test/test_read_format_7zip_copy_2.7z.uu \
        libarchive/test/test_read_format_7zip_deflate.7z.uu \
+       libarchive/test/test_read_format_7zip_deflate_arm64.7z.uu \
        libarchive/test/test_read_format_7zip_delta_lzma1.7z.uu \
        libarchive/test/test_read_format_7zip_delta4_lzma1.7z.uu \
        libarchive/test/test_read_format_7zip_delta_lzma2.7z.uu \
@@ -780,8 +781,9 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_7zip_lzma1.7z.uu \
        libarchive/test/test_read_format_7zip_lzma1_2.7z.uu \
        libarchive/test/test_read_format_7zip_lzma1_lzma2.7z.uu \
-       libarchive/test/test_read_format_7zip_lzma2_arm.7z.uu \
        libarchive/test/test_read_format_7zip_lzma2.7z.uu \
+       libarchive/test/test_read_format_7zip_lzma2_arm64.7z.uu \
+       libarchive/test/test_read_format_7zip_lzma2_arm.7z.uu \
        libarchive/test/test_read_format_7zip_malformed.7z.uu \
        libarchive/test/test_read_format_7zip_malformed2.7z.uu \
        libarchive/test/test_read_format_7zip_packinfo_digests.7z.uu \
index a2a0a5c25be3faa0744a91b58cb0d5b514a3d15a..933fea59ead9e30f820558117a0762f99a405955 100644 (file)
@@ -482,6 +482,19 @@ if test "x$with_lzma" != "xno"; then
   if test "x$ac_cv_lzma_has_mt" != xno; then
          AC_DEFINE([HAVE_LZMA_STREAM_ENCODER_MT], [1], [Define to 1 if you have the `lzma_stream_encoder_mt' function.])
   fi
+
+  AC_CACHE_CHECK(
+    [whether we have ARM64 filter support in lzma],
+    ac_cv_lzma_has_arm64,
+    [AC_LINK_IFELSE([
+      AC_LANG_PROGRAM([[#include <lzma.h>]
+                       [#ifndef LZMA_FILTER_ARM64]
+                       [#error unsupported]
+                       [#endif]])],
+      [ac_cv_lzma_has_arm64=yes], [ac_cv_lzma_has_arm64=no])])
+  if test "x$ac_cv_lzma_has_arm64" != xno; then
+         AC_DEFINE([HAVE_LZMA_FILTER_ARM64], [1], [Define to 1 if you have the `LZMA_FILTER_ARM64' macro.])
+  fi
 fi
 
 AC_ARG_WITH([lzo2],
index df6f035ed782af369bf2acb0d73e3a2b3eb07e5e..bb595b3e4b07cb4c7e3b6c18e957a4734f10e054 100644 (file)
@@ -83,6 +83,7 @@ __FBSDID("$FreeBSD$");
 #define _7Z_IA64       0x03030401
 #define _7Z_ARM                0x03030501
 #define _7Z_ARMTHUMB   0x03030701
+#define _7Z_ARM64      0xa
 #define _7Z_SPARC      0x03030805
 
 #define _7Z_ZSTD       0x4F71101 /* Copied from https://github.com/mcmilk/7-Zip-zstd.git */
@@ -409,6 +410,7 @@ static void x86_Init(struct _7zip *);
 static size_t  x86_Convert(struct _7zip *, uint8_t *, size_t);
 static void    arm_Init(struct _7zip *);
 static size_t  arm_Convert(struct _7zip *, uint8_t *, size_t);
+static size_t  arm64_Convert(struct _7zip *, uint8_t *, size_t);
 static ssize_t         Bcj2_Decode(struct _7zip *, uint8_t *, size_t);
 
 
@@ -1044,7 +1046,8 @@ init_decompression(struct archive_read *a, struct _7zip *zip,
                if (coder2 != NULL) {
                        if (coder2->codec != _7Z_X86 &&
                            coder2->codec != _7Z_X86_BCJ2 &&
-                           coder2->codec != _7Z_ARM) {
+                           coder2->codec != _7Z_ARM &&
+                           coder2->codec != _7Z_ARM64) {
                                archive_set_error(&a->archive,
                                    ARCHIVE_ERRNO_MISC,
                                    "Unsupported filter %lx for %lx",
@@ -1153,6 +1156,12 @@ init_decompression(struct archive_read *a, struct _7zip *zip,
                                filters[fi].id = LZMA_FILTER_ARMTHUMB;
                                fi++;
                                break;
+#ifdef LZMA_FILTER_ARM64
+                       case _7Z_ARM64:
+                               filters[fi].id = LZMA_FILTER_ARM64;
+                               fi++;
+                               break;
+#endif
                        case _7Z_SPARC:
                                filters[fi].id = LZMA_FILTER_SPARC;
                                fi++;
@@ -1620,7 +1629,7 @@ decompress(struct archive_read *a, struct _7zip *zip,
        /*
         * Decord BCJ.
         */
-       if (zip->codec != _7Z_LZMA2 && (zip->codec2 == _7Z_X86 || zip->codec2 == _7Z_ARM)) {
+       if (zip->codec != _7Z_LZMA2) {
                if (zip->codec2 == _7Z_X86) {
                        size_t l = x86_Convert(zip, buff, *outbytes);
 
@@ -1632,8 +1641,10 @@ decompress(struct archive_read *a, struct _7zip *zip,
                                *outbytes = l;
                        } else
                                zip->odd_bcj_size = 0;
-               } else { // zip->codec2 == _7Z_ARM
+               } else if (zip->codec2 == _7Z_ARM) {
                        *outbytes = arm_Convert(zip, buff, *outbytes);
+               } else if (zip->codec2 == _7Z_ARM64) {
+                       *outbytes = arm64_Convert(zip, buff, *outbytes);
                }
        }
 
@@ -3827,6 +3838,69 @@ arm_Convert(struct _7zip *zip, uint8_t *buf, size_t size)
        return i;
 }
 
+static size_t
+arm64_Convert(struct _7zip *zip, uint8_t *buf, size_t size)
+{
+       // This function was adapted from
+       // static size_t bcj_arm64(struct xz_dec_bcj *s, uint8_t *buf, size_t size)
+       // in https://git.tukaani.org/xz-embedded.git
+
+       /*
+        * Branch/Call/Jump (BCJ) filter decoders
+        *
+        * Authors: Lasse Collin <lasse.collin@tukaani.org>
+        *          Igor Pavlov <https://7-zip.org/>
+        *
+        * This file has been put into the public domain.
+        * You can do whatever you want with this file.
+        */
+
+       size_t i;
+       uint32_t instr;
+       uint32_t addr;
+
+       for (i = 0; i + 4 <= size; i += 4) {
+               instr = (uint32_t)buf[i]
+                       | ((uint32_t)buf[i+1] << 8)
+                       | ((uint32_t)buf[i+2] << 16)
+                       | ((uint32_t)buf[i+3] << 24);
+
+               if ((instr >> 26) == 0x25) {
+                       /* BL instruction */
+                       addr = instr - ((zip->bcj_ip + (uint32_t)i) >> 2);
+                       instr = 0x94000000 | (addr & 0x03FFFFFF);
+
+                       buf[i]   = (uint8_t)instr;
+                       buf[i+1] = (uint8_t)(instr >> 8);
+                       buf[i+2] = (uint8_t)(instr >> 16);
+                       buf[i+3] = (uint8_t)(instr >> 24);
+               } else if ((instr & 0x9F000000) == 0x90000000) {
+                       /* ADRP instruction */
+                       addr = ((instr >> 29) & 3) | ((instr >> 3) & 0x1FFFFC);
+
+                       /* Only convert values in the range +/-512 MiB. */
+                       if ((addr + 0x020000) & 0x1C0000)
+                               continue;
+
+                       addr -= (zip->bcj_ip + (uint32_t)i) >> 12;
+
+                       instr &= 0x9000001F;
+                       instr |= (addr & 3) << 29;
+                       instr |= (addr & 0x03FFFC) << 3;
+                       instr |= (0U - (addr & 0x020000)) & 0xE00000;
+
+                       buf[i]   = (uint8_t)instr;
+                       buf[i+1] = (uint8_t)(instr >> 8);
+                       buf[i+2] = (uint8_t)(instr >> 16);
+                       buf[i+3] = (uint8_t)(instr >> 24);
+               }
+       }
+
+       zip->bcj_ip += i;
+
+       return i;
+}
+
 /*
  * Brought from LZMA SDK.
  *
index 5f97b6a0d57f9e41876038fefd606a6dba57bd72..1eca3936e5b46fbe389fa5995d2339610d483390 100644 (file)
@@ -1110,3 +1110,75 @@ DEFINE_TEST(test_read_format_7zip_ppmd)
 {
        test_ppmd();
 }
+
+static void
+test_arm64_filter(const char *refname)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       char buff[70368];
+       uint32_t computed_crc = 0;
+       uint32_t expected_crc = 0xde97d594;
+
+       assert((a = archive_read_new()) != NULL);
+
+       extract_reference_file(refname);
+
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+
+       assertEqualIntA(a, ARCHIVE_OK,
+               archive_read_open_filename(a, refname, 10240));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualInt((AE_IFREG | 0775), archive_entry_mode(ae));
+       assertEqualString("hw-arm64", archive_entry_pathname(ae));
+       assertEqualInt(sizeof(buff), archive_entry_size(ae));
+       assertEqualInt(sizeof(buff), archive_read_data(a, buff, sizeof(buff)));
+       computed_crc = crc32(computed_crc, buff, sizeof(buff));
+       assertEqualInt(computed_crc, expected_crc);
+
+       assertEqualInt(1, archive_file_count(a));
+
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+       assertEqualInt(ARCHIVE_OK, archive_read_close(a));
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_lzma2_arm64)
+{
+#ifdef HAVE_LZMA_FILTER_ARM64
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       if (ARCHIVE_OK != archive_read_support_filter_lzma(a)) {
+               skipping(
+                   "7zip:lzma decoding is not supported on this platform");
+       } else {
+               test_arm64_filter("test_read_format_7zip_lzma2_arm64.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+#else
+       skipping("This version of liblzma does not support LZMA_FILTER_ARM64");
+#endif
+}
+
+DEFINE_TEST(test_read_format_7zip_deflate_arm64)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       if (ARCHIVE_OK != archive_read_support_filter_gzip(a)) {
+               skipping(
+                   "7zip:deflate decoding is not supported on this platform");
+       } else {
+               test_arm64_filter("test_read_format_7zip_deflate_arm64.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
diff --git a/libarchive/test/test_read_format_7zip_deflate_arm64.7z.uu b/libarchive/test/test_read_format_7zip_deflate_arm64.7z.uu
new file mode 100644 (file)
index 0000000..e516ca5
--- /dev/null
@@ -0,0 +1,64 @@
+begin 664 libarchive/test/test_read_format_7zip_deflate_arm64.7z
+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
+3`#)5M-E?M]D!%08!`""`_8$`````
+`
+end
diff --git a/libarchive/test/test_read_format_7zip_lzma2_arm64.7z.uu b/libarchive/test/test_read_format_7zip_lzma2_arm64.7z.uu
new file mode 100644 (file)
index 0000000..3792a18
--- /dev/null
@@ -0,0 +1,54 @@
+begin 664 libarchive/test/test_read_format_7zip_lzma2_arm64.7z
+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
+C`'(`;0`V`#0````9`!0*`0`R5;397[?9`14&`0`@@/V!````
+`
+end