Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@
<artifactId>sevenzipjbinding-all-platforms</artifactId>
<version>${sevenzipjbinding.version}</version>
</dependency>
<!-- all-platforms 不包含 Linux arm64 的原生库,需单独引入。
两个 jar 各带一份同名的平台列表文件,SevenZipJBinding 只读取类路径上的第一份,
因此必须放在 all-platforms 之后,否则其它平台也会去加载 arm64 的原生库。
平台名的指定见 cn.keking.utils.SevenZipNativePlatform -->
<dependency>
<groupId>net.sf.sevenzipjbinding</groupId>
<artifactId>sevenzipjbinding-linux-arm64</artifactId>
<version>${sevenzipjbinding.version}</version>
</dependency>
<dependency>
<groupId>com.github.junrar</groupId>
<artifactId>junrar</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cn.keking.model.FileAttribute;
import cn.keking.model.FileType;
import cn.keking.utils.RarUtils;
import cn.keking.utils.SevenZipNativePlatform;
import cn.keking.web.filter.BaseUrlFilter;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IInArchive;
Expand Down Expand Up @@ -34,6 +35,11 @@
*/
@Component
public class CompressFileReader {
static {
// Linux arm64 上需要显式指定 SevenZipJBinding 的平台名,其它平台不做任何处理
SevenZipNativePlatform.init();
}

private final FileHandlerService fileHandlerService;

public CompressFileReader(FileHandlerService fileHandlerService) {
Expand Down
61 changes: 61 additions & 0 deletions server/src/main/java/cn/keking/utils/SevenZipNativePlatform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cn.keking.utils;

import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 在 Linux arm64(aarch64)上为 SevenZipJBinding 指定原生库所在的平台名。
*
* SevenZipJBinding 自动选择平台时,用 os.name 的第一个单词 + "-" + os.arch 作为平台名,
* 在 aarch64 的 Linux 上得到的是 Linux-aarch64,而 arm64 原生库所在的目录是 Linux-arm64,
* 两者不一致,即使原生库已在类路径上也会提示 Can't find suited platform,因此需要显式指定。
* 其它平台不做任何处理,仍由 SevenZipJBinding 自动选择。
*/
public final class SevenZipNativePlatform {

private static final Logger logger = LoggerFactory.getLogger(SevenZipNativePlatform.class);

/**
* sevenzipjbinding-linux-arm64 中原生库所在的目录名,也是初始化时使用的平台名
*/
static final String LINUX_ARM64 = "Linux-arm64";

private SevenZipNativePlatform() {
}

/**
* 获取需要显式指定的平台名
*
* @param osName 系统属性 os.name
* @param osArch 系统属性 os.arch
* @return 平台名,返回 null 表示不指定,由 SevenZipJBinding 自动选择
*/
static String explicitPlatform(String osName, String osArch) {
if (osName == null || osArch == null) {
return null;
}
// 与 SevenZipJBinding 一致,只取 os.name 的第一个单词
if ("Linux".equals(osName.split(" ")[0]) && "aarch64".equals(osArch)) {
return LINUX_ARM64;
}
return null;
}

/**
* 在首次使用 SevenZip 之前调用。初始化失败只记录日志,不影响应用启动
*/
public static void init() {
String platform = explicitPlatform(System.getProperty("os.name"), System.getProperty("os.arch"));
if (platform == null) {
return;
}
try {
SevenZip.initSevenZipFromPlatformJAR(platform);
logger.info("SevenZipJBinding 原生库加载成功,平台: {}", platform);
} catch (SevenZipNativeInitializationException | RuntimeException e) {
logger.error("SevenZipJBinding 原生库加载失败,平台: {},压缩文件将无法预览", platform, e);
}
}
}
101 changes: 101 additions & 0 deletions server/src/test/java/cn/keking/utils/SevenZipNativePlatformTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package cn.keking.utils;

import net.sf.sevenzipjbinding.SevenZip;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Linux arm64(aarch64)压缩包预览回归测试。
*
* 背景:sevenzipjbinding-all-platforms 不包含 Linux arm64 的原生库,并且 SevenZipJBinding 按
* os.name + "-" + os.arch 拼出的平台名是 Linux-aarch64,与原生库所在目录 Linux-arm64 不一致,
* 导致 aarch64 的 Linux 上所有压缩包都无法预览。
*
* 运行测试的机器通常不是 arm64,无法真正加载该原生库,这里校验加载之前的各个环节:
* 原生库在类路径上且是 AArch64 的 ELF 文件;Linux + aarch64 时指定的平台名与原生库所在目录一致;
* 其它平台不指定平台名,仍由 SevenZipJBinding 自动选择。
*/
class SevenZipNativePlatformTests {

private static final int ELF_MACHINE_X86_64 = 62;
private static final int ELF_MACHINE_AARCH64 = 183;

@Test
void shouldShipLinuxArm64NativeLibrary() throws Exception {
assertEquals(ELF_MACHINE_AARCH64, elfMachineOfNativeLibrary("Linux-arm64"));
}

@Test
void shouldSelectLinuxArm64PlatformOnLinuxAarch64() throws Exception {
String platform = SevenZipNativePlatform.explicitPlatform("Linux", "aarch64");

assertEquals("Linux-arm64", platform);
// 指定的平台名必须能定位到 AArch64 的原生库
assertEquals(ELF_MACHINE_AARCH64, elfMachineOfNativeLibrary(platform));
}

@Test
void shouldLeaveOtherPlatformsToAutomaticSelection() {
assertNull(SevenZipNativePlatform.explicitPlatform("Linux", "amd64"));
assertNull(SevenZipNativePlatform.explicitPlatform("Linux", "i386"));
assertNull(SevenZipNativePlatform.explicitPlatform("Linux", "arm"));
assertNull(SevenZipNativePlatform.explicitPlatform("Windows 11", "amd64"));
assertNull(SevenZipNativePlatform.explicitPlatform("Windows Server 2022", "aarch64"));
assertNull(SevenZipNativePlatform.explicitPlatform("Mac OS X", "aarch64"));
assertNull(SevenZipNativePlatform.explicitPlatform("Mac OS X", "x86_64"));
assertNull(SevenZipNativePlatform.explicitPlatform(null, null));
}

/**
* 对照:原有平台仍在 SevenZipJBinding 读取到的平台列表中,原生库也仍能取到。
* 如果 arm64 的 jar 在类路径上排到 all-platforms 之前,平台列表会只剩 Linux-arm64 一项,
* 所有平台都会去加载 arm64 的原生库。
*/
@Test
void shouldKeepExistingPlatformsAvailable() throws Exception {
List<String> platforms = SevenZip.getPlatformList();

assertTrue(platforms.contains("Windows-amd64"), () -> "listed platforms: " + platforms);
assertTrue(platforms.contains("Linux-amd64"), () -> "listed platforms: " + platforms);
assertNotNull(nativeLibraryHeader("Windows-amd64"));
assertEquals(ELF_MACHINE_X86_64, elfMachineOfNativeLibrary("Linux-amd64"));
}

private static int elfMachineOfNativeLibrary(String platform) throws IOException {
byte[] header = nativeLibraryHeader(platform);
assertTrue(header[0] == 0x7f && header[1] == 'E' && header[2] == 'L' && header[3] == 'F',
() -> platform + " native library is not an ELF file");
return (header[18] & 0xff) | ((header[19] & 0xff) << 8);
}

/**
* 与 SevenZipJBinding 定位原生库的方式一致:先读取 /平台名/sevenzipjbinding-lib.properties,
* 再按其中的 lib.1.name 读取文件。
*/
private static byte[] nativeLibraryHeader(String platform) throws IOException {
assertNotNull(platform, "no platform name");
String directory = "/" + platform + "/";
Properties properties = new Properties();
try (InputStream inputStream = SevenZip.class.getResourceAsStream(directory + "sevenzipjbinding-lib.properties")) {
assertNotNull(inputStream, () -> directory + "sevenzipjbinding-lib.properties is not on the classpath");
properties.load(inputStream);
}
String libraryName = properties.getProperty("lib.1.name");
assertNotNull(libraryName, () -> directory + "sevenzipjbinding-lib.properties has no lib.1.name");
try (InputStream inputStream = SevenZip.class.getResourceAsStream(directory + libraryName)) {
assertNotNull(inputStream, () -> directory + libraryName + " is not on the classpath");
byte[] header = inputStream.readNBytes(20);
assertEquals(20, header.length, () -> directory + libraryName + " is shorter than an ELF header");
return header;
}
}
}
Loading