]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
7zip reader: add SPARC filter support for non-LZMA compressors (#2399)
authorMostyn Bramley-Moore <mostyn@antipode.se>
Tue, 29 Oct 2024 02:29:23 +0000 (03:29 +0100)
committerGitHub <noreply@github.com>
Tue, 29 Oct 2024 02:29:23 +0000 (19:29 -0700)
These two new test archives contain a C hello world executable built
like so on a ubuntu 24.04 machine:

```
#include <stdio.h>

int main(int argc, char *argv[]) {
  printf("hello, world\n");
  return 0;
}
```

`sparc64-linux-gnu-gcc hw.c -o hw-sparc64 -Wall`

The two test archives that contain this executable were created like so,
using the https://github.com/tehmul/p7zip-zstd fork of 7-Zip:
`7z a -t7z -m0=zstd -mf=SPARC
libarchive/test/test_read_format_7zip_zstd_sparc.7z hw-sparc64`
`7z a -t7z -m0=lzma2 -mf=SPARC
libarchive/test/test_read_format_7zip_lzma2_sparc.7z hw-sparc64`

Two test files are required, because the 7zip reader code has two
different paths, one for lzma and one for all other compressors.

The test_read_format_7zip_lzma2_sparc test is expected to pass, because
LZMA BCJ filters are implemented in liblzma.

The test_read_format_7zip_zstd_sparc test is expected to fail in the
first commit, because libarchive does not currently implement the SPARC
BCJ filter. The second commit will make test_read_format_7zip_zstd_sparc
pass.

Makefile.am
libarchive/archive_read_support_format_7zip.c
libarchive/test/test_read_format_7zip.c
libarchive/test/test_read_format_7zip_lzma2_sparc.7z.uu [new file with mode: 0644]
libarchive/test/test_read_format_7zip_zstd_sparc.7z.uu [new file with mode: 0644]

index c72ed70f7df6694cbdb7ebec17d0babe957def03..8f175f68fb8b17ad725a0b5dd75c2da8713bea07 100644 (file)
@@ -806,6 +806,7 @@ libarchive_test_EXTRA_DIST=\
        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_lzma2_sparc.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 \
@@ -816,6 +817,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_7zip_zstd_arm.7z.uu \
        libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu \
        libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu \
+       libarchive/test/test_read_format_7zip_zstd_sparc.7z.uu \
        libarchive/test/test_read_format_7zip_zstd.7z.uu \
        libarchive/test/test_read_format_ar.ar.uu \
        libarchive/test/test_read_format_cab_1.cab.uu \
index b4e34d68dbba6255ab974775eca7a166ca4e5b4f..62e9a2b1cf108dbfea42015282acfa29aa321b90 100644 (file)
@@ -435,6 +435,7 @@ 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);
+static size_t  sparc_Convert(struct _7zip *, uint8_t *, size_t);
 
 
 int
@@ -770,7 +771,7 @@ archive_read_format_7zip_read_header(struct archive_read *a,
                /* allocate for ",rdonly,hidden,system" */
                fflags_text = malloc(22 * sizeof(*fflags_text));
                if (fflags_text != NULL) {
-                       ptr = fflags_text; 
+                       ptr = fflags_text;
                        if (zip_entry->attr & FILE_ATTRIBUTE_READONLY) {
                                strcpy(ptr, ",rdonly");
                                ptr = ptr + 7;
@@ -1109,7 +1110,8 @@ init_decompression(struct archive_read *a, struct _7zip *zip,
                        if (coder2->codec != _7Z_X86 &&
                            coder2->codec != _7Z_X86_BCJ2 &&
                            coder2->codec != _7Z_ARM &&
-                           coder2->codec != _7Z_ARM64) {
+                           coder2->codec != _7Z_ARM64 &&
+                           coder2->codec != _7Z_SPARC) {
                                archive_set_error(&a->archive,
                                    ARCHIVE_ERRNO_MISC,
                                    "Unsupported filter %lx for %lx",
@@ -1708,6 +1710,8 @@ decompress(struct archive_read *a, struct _7zip *zip,
                        *outbytes = arm_Convert(zip, buff, *outbytes);
                } else if (zip->codec2 == _7Z_ARM64) {
                        *outbytes = arm64_Convert(zip, buff, *outbytes);
+               } else if (zip->codec2 == _7Z_SPARC) {
+                       *outbytes = sparc_Convert(zip, buff, *outbytes);
                }
        }
 
@@ -4007,6 +4011,64 @@ arm64_Convert(struct _7zip *zip, uint8_t *buf, size_t size)
        return i;
 }
 
+static size_t
+sparc_Convert(struct _7zip *zip, uint8_t *buf, size_t size)
+{
+       // This function was adapted from
+       // static size_t bcj_sparc(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/>
+        *
+        * Copyright (C) The XZ Embedded authors and contributors
+        *
+        * Permission to use, copy, modify, and/or distribute this
+        * software for any purpose with or without fee is hereby granted.
+        *
+        * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+        * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+        * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+        * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+        * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+        * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+        * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+        * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+        */
+
+       size_t i;
+       uint32_t instr;
+
+       size &= ~(size_t)3;
+
+       for (i = 0; i < size; i += 4) {
+               instr = (uint32_t)(buf[i] << 24)
+                       | ((uint32_t)buf[i+1] << 16)
+                       | ((uint32_t)buf[i+2] << 8)
+                       | (uint32_t)buf[i+3];
+
+               if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) {
+                       instr <<= 2;
+                       instr -= zip->bcj_ip + (uint32_t)i;
+                       instr >>= 2;
+                       instr = ((uint32_t)0x40000000 - (instr & 0x400000))
+                               | 0x40000000 | (instr & 0x3FFFFF);
+
+                       buf[i] = (uint8_t)(instr >> 24);
+                       buf[i+1] = (uint8_t)(instr >> 16);
+                       buf[i+2] = (uint8_t)(instr >> 8);
+                       buf[i+3] = (uint8_t)instr;
+               }
+       }
+
+       zip->bcj_ip += (uint32_t)i;
+
+       return i;
+}
+
 /*
  * Brought from LZMA SDK.
  *
index bb47be668286ed133f63667d785b1e1c96d6f20b..e2eaf48891ad493935286db876c2859c94965500 100644 (file)
@@ -1301,3 +1301,76 @@ DEFINE_TEST(test_read_format_7zip_extract_second)
 
        assertEqualInt(ARCHIVE_OK, archive_read_free(a));
 }
+
+static void
+test_sparc_filter(const char *refname)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       size_t expected_entry_size = 1053016;
+       char *buff = malloc(expected_entry_size);
+       uint32_t computed_crc = 0;
+       uint32_t expected_crc = 0x6b5b364d;
+
+       assert((a = archive_read_new()) != NULL);
+
+       extract_reference_file(refname);
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+       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-sparc64", archive_entry_pathname(ae));
+       assertEqualInt(expected_entry_size, archive_entry_size(ae));
+       assertEqualInt(expected_entry_size, archive_read_data(a, buff, expected_entry_size));
+
+       computed_crc = crc32(computed_crc, buff, expected_entry_size);
+       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));
+
+       free(buff);
+}
+
+DEFINE_TEST(test_read_format_7zip_lzma2_sparc)
+{
+       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_sparc_filter("test_read_format_7zip_lzma2_sparc.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
+
+DEFINE_TEST(test_read_format_7zip_zstd_sparc)
+{
+       struct archive *a;
+
+       assert((a = archive_read_new()) != NULL);
+
+       if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) {
+               skipping(
+                   "7zip:zstd decoding is not supported on this platform");
+       } else {
+               test_sparc_filter("test_read_format_7zip_zstd_sparc.7z");
+       }
+
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}
diff --git a/libarchive/test/test_read_format_7zip_lzma2_sparc.7z.uu b/libarchive/test/test_read_format_7zip_lzma2_sparc.7z.uu
new file mode 100644 (file)
index 0000000..2ebafa0
--- /dev/null
@@ -0,0 +1,51 @@
+begin 664 test_read_format_7zip_lzma2_sparc.7z
+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
+M!0$9#P```````````````````!$7`&@`=P`M`',`<`!A`'(`8P`V`#0````9
+;!``````4"@$`@,:@07(HVP$5!@$`((#]@0``
+`
+end
diff --git a/libarchive/test/test_read_format_7zip_zstd_sparc.7z.uu b/libarchive/test/test_read_format_7zip_zstd_sparc.7z.uu
new file mode 100644 (file)
index 0000000..ae94baa
--- /dev/null
@@ -0,0 +1,59 @@
+begin 664 test_read_format_7zip_zstd_sparc.7z
+M-WJ\KR<<``3"/F,50PD```````!R`````````&JZD5PHM2_]`$@!``!0*DT8
+M!````"X)```HM2_]H%@1$`#,%P#VY81(\-"H-?,C>D3."L@6'/;(Z"_OIG-<
+M&K,3C)Y#S,RFL.J-K#=E?%`M<*T];K]*&Z!'F&=H'7BNL.%[O^W'36\1$@F5
+ML25C;RE39P!U`'$`MAH8R!8>B!DH;E@X$*JU?FFQ@'_(SU+1;*J;FIG9V2!V
+MDB8GL-E)TFW5<'2+V0Q'MUAHX5$A^4D?7\5P\H?T8?KIK?2V@XK\%?S%>_3P
+M+_T%-O!='.5[_3<`L!<7VI_!=5KKBLL%G+V.,&3U)2MV?$KV3YUF,P&H!Z]3
+M#V>,K8R]8FADM[*\G*')?"C+31ZP5*Q2I4*+"\LG*II-!0.;G<_,7C+EC>9U
+MLY4PL-E9EMMN-;4=Q='5J?0(P;3R_P&0$#\BR-8$'!U_Z)J6?EI#$`1?6BST
+MMRD5"0.0`B^;J_?<6%.@LVES/Y*3[@?I53>N%NP\GG1[@.PQZ"(R`BHM6XWT
+MP""QZ$6.N7CIUL2C+?%H?4)2)QT-!Q'B@212\#C06.T0_+QU82-'+(*1\]/:
+MXOUI.*YN[G"TU\$GFJC<#_:,1()6])MDFG01&0$%L9.&9C.6T,XS1G9U$1D!
+MT:V*312=1RI;O";,ZF@XB,-+X!O!L[F!VN9DY*#9>P1ROQ?IHZ/-H=#O/6WN
+M<+_7AX]>T]!8(UMLC@S]$:ZOTEI*#[V5AT*-U0/64G3$F^+YO2S/C^2D$XMB
+M?'LG;J]N!MBLXMWA?@%KJ`$]!%%)0"-!"HJ:M`'``J$9*FX2X`!1J"\5929=
+M4%!.O2K+_AF)^77`/:-'"SXW\6%YIU\F^,G;UASI9MI_Z9;A^(47[B^6XR"F
+M)5>`7MKK@4\P`2F&4I)_/6!LZWDI8IMCY5@U4[/JEM;N?MIKQ5$9,RTVTNEX
+MG;Z-F@\+0`!*3I@5-@FGL?SC,_@K$;VM_4S_7>XI!WD*?B^HQ%Q3$`@KC"+'
+MCR'^M.NC^7L!=`08@Z(RCIUT3^?(6X6B),J1MP"Q/(MO/WYIR]X_$RA(U5#O
+MI9<G/X_"ZSI[*IQ(D;88*`(`$``"`!```@`0``(`$``"`!```@`0`,P&`(0%
+M`!`&!<@```````P0!#@```T0!D@``!D`&B_]^```'`00`F!O__[U$`*0!0-(
+M$`*X"E0+&```,!07$`/P$`/88```"1AP<&____X0`ZAO____`6___Q`#G#<@
+M@09"(B/Q]P'8@&W$L7<2-DRC_0-PMUT,_=^?Q9QS!;&NSPS_9V#-2/<V^&\C
+MM!M],<I_-4C2_V3>_'^@"ZOZ5T"^S&29;WDF^K]`P(--U]F:3-Q0PHPW^EZQ
+MW?\>+/@?<4/]3^'&-E#&\(W8=$S_.Z:5?V(;YW^WQ??G@Q"5*0"F\:)&\)23
+MQJP5#??[N\QK\8K/R8D['_38TJ)4>6XX00;0;J0:4<^0-42()$,8$.<$/@?X
+M-]A*ERF\-K>$<><HO+N%>,XR9C?9*9``E`"3`']06$@RE)4>=$H\+#(34:E)
+MD",,4H#T\<D<+)%#9!#)0^*P(5F`5,B;7\J8'RLH``UX,@:B?R1#2F)_Y'-)
+MFO-(4H*?STG3P,]%/W_N`FES$2"MP,\)_'P^G\\YG\_GW)!I,]/FVJ3`\KJP
+MHD*B*_2"A=&K@J\4B`DC#283C)!82/+QLT@L).G\/!(+23D_#R[[GE`MI9L]
+MP90ZT,B@]F0#PN5DJK'QL.^ZT)SV-%.R>=F=HR=6]0._W1W6:KVZ]5J;B#-,
+MK*"WL$T4M_W`#V=)M/.JQW*U0FX4KVU',:S=LK94R%)MV.T^>F#(XK955_3J
+M^*D\T>OF*`[.JXY5Y%9M=;_4MGM(+521.V%00V:9B$8,^2P'0"YZ<N231!KD
+MWT13(0]_X(=#KRB\2NF5Y0)A'=U8GD@7%PR_4?1,B+4%&T%81U^-VVK55E5D
+M6DK=I]*QOE>*OD`5]6H(TEVB5\%O)=:1GMIJ"S;2^H&?:>9F=QQ3NW>@MW?>
+M^%KPZ^JX-3*[Q^K`;;7AV*UL=>MV?ZM;YW'(P*(EP->J+N?:ZJF6$S?7N#>G
+M#R\O-8LJM=2@8':3S$R)9D1)O@`:BLPA3[29$JKP*_AU`&01-J+U$-&TG7_$
+M(^5)BS^D!?DU($V:W.,/K>,;H4QGQNB'S)'Q_WG0"P0Q/3TM*_Z4E)V=%*&3
+M$G^6#<A(_,L/\:?`V+%CFO\)9TQBL=#E_X`^B_F\`</_9P@_^,\65-!KM4*.
+M/HXI>@U5=.YK;2"L6WDT%&F!4*@A86:&1)*"@I0RK`%Q!08AA!2J,V5U$D"Q
+M0"1L65R9H1$2B5,_MC8#:.`!CH&`8X3!\O=`W"N)K*++$/418+A*NBEL:!0-
+M,:)]QOG,8+P[Q596>NE0>)@R]!R(D<5G>&C',BX,>MP+%Z#R`SF$D[#G!7A<
+MK$WSC0;<'+!<;C>MS((#J%_WY_.':K+X#YNUOTE,2LZQXPR@:%Z3182?V20/
+M+NM&7>9T6=#7T")Z!S_,?U3.=0$&+.I9E/_!AE_UK+482*C309H.X/.8NO]A
+MXS8<5[@W$$8#R)4[!)]8`,6A8+/B5R_Z#S*;UL5;*7WFT%MR,=B$;I/+E`Q6
+M_$;&<LHPW\F-3`NX@W4>CT5P[0*I/&R8BXFXS^O2%G$)?#T`,ALPS[61>02@
+M'N%$.9)8Z>E!GJ^`S_M-"-`BV-U9ISY8-0`0P#D<3DRRJ`!_)1-9IO1-6?,V
+M8Y4>V5$$UZ,>%_=48D5U8P**U_GCO*:GEK+\.MTVVX0N@!?.U[,7P']+9YF,
+M!4!6JSA;``\O7`'\G\B+C!7`R\U=`?RD"*%K.8`=D:(J%9K16'"C!,`J=AAM
+M+),P?%7]6?!PW78<:++M.^&QC09HV6)V`$PH]VCY4ZEXX*\!;Z;?A!>^,`":
+M`+(=Z:&T/^K,^OX$SNV[PRW3ANQZ\U6;MG#C$F-JD*'0YZ/]?57+NMG:&/+4
+M,%:\T9IL$9.A]`EQ"-108&:J,A1P*/4^H5U/W))T+V:-`C!8I"#M_T1/D[T"
+M`,*+WPN,7"HVPM``,,ZTX&^E<YL3[,9(N+D^MT4D=1Q,FIW=I7J3G!S,`+(;
+M:,`"Y3,GJ!H`@GHM0%0L=+"=J<4DF/M_257I?]QS7D/::E8*?A[&&M'$+[KP
+M(O`![\GAEO])A",Z[?^"*;$$`00&``$)B4,`!PL!``(D!/<1`04!!0,```0#
+M`P@%`0`,T%@1T%@1``@*`4TV6VL```4!&0@``````````!$7`&@`=P`M`',`
+J<`!A`'(`8P`V`#0````9!``````4"@$`@,:@07(HVP$5!@$`((#]@0``
+`
+end