From 1c6e54b38f489cdc44a105601f62b9e995949673 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Wed, 10 May 2023 10:12:43 +0200 Subject: [PATCH] Better native library loading --- .../it/tdlight/example/AdvancedExample.java | 2 +- .../main/java/it/tdlight/example/Example.java | 2 +- .../{utils => util}/LibraryVersion.java | 2 +- .../src/main/java/it/tdlight/ArrayUtil.java | 2 +- .../main/java/it/tdlight/ClientFactory.java | 12 +- .../java/it/tdlight/ConstructorDetector.java | 7 +- .../src/main/java/it/tdlight/Init.java | 15 +- .../src/main/java/it/tdlight/Log.java | 2 +- .../java/it/tdlight/ResponseReceiver.java | 5 +- .../ConsoleInteractiveAuthenticationData.java | 3 +- .../client/ScannerClientInteraction.java | 2 +- .../tdlight/client/SimpleTelegramClient.java | 8 +- .../java/it/tdlight/client/TDLibSettings.java | 2 +- .../tdlight/{utils => util}/CleanSupport.java | 2 +- .../tdlight/{utils => util}/IntSwapper.java | 2 +- .../src/main/java/it/tdlight/util/Native.java | 166 +++++ .../it/tdlight/util/NativeLibraryLoader.java | 598 ++++++++++++++++++ .../it/tdlight/util/NativeLibraryUtil.java | 30 + .../tdlight/{utils => util}/ScannerUtils.java | 2 +- .../{utils => util}/SpinWaitSupport.java | 2 +- .../TDLightBlockHoundIntegration.java | 2 +- .../UnsupportedNativeLibraryException.java} | 20 +- .../java/it/tdlight/utils/LoadLibrary.java | 239 ------- tdlight-java/src/main/java/module-info.java | 2 +- .../tdlight/{utils => util}/CleanSupport.java | 2 +- .../{utils => util}/SpinWaitSupport.java | 2 +- ...ockhound.integration.BlockHoundIntegration | 2 +- 27 files changed, 843 insertions(+), 292 deletions(-) rename tdlight-java/src/main/java-templates/it/tdlight/{utils => util}/LibraryVersion.java (89%) rename tdlight-java/src/main/java/it/tdlight/{utils => util}/CleanSupport.java (88%) rename tdlight-java/src/main/java/it/tdlight/{utils => util}/IntSwapper.java (90%) create mode 100644 tdlight-java/src/main/java/it/tdlight/util/Native.java create mode 100644 tdlight-java/src/main/java/it/tdlight/util/NativeLibraryLoader.java create mode 100644 tdlight-java/src/main/java/it/tdlight/util/NativeLibraryUtil.java rename tdlight-java/src/main/java/it/tdlight/{utils => util}/ScannerUtils.java (98%) rename tdlight-java/src/main/java/it/tdlight/{utils => util}/SpinWaitSupport.java (89%) rename tdlight-java/src/main/java/it/tdlight/{utils => util}/TDLightBlockHoundIntegration.java (94%) rename tdlight-java/src/main/java/it/tdlight/{utils/CantLoadLibrary.java => util/UnsupportedNativeLibraryException.java} (59%) delete mode 100644 tdlight-java/src/main/java/it/tdlight/utils/LoadLibrary.java rename tdlight-java/src/main/java11/it/tdlight/{utils => util}/CleanSupport.java (92%) rename tdlight-java/src/main/java11/it/tdlight/{utils => util}/SpinWaitSupport.java (84%) diff --git a/example/src/main/java/it/tdlight/example/AdvancedExample.java b/example/src/main/java/it/tdlight/example/AdvancedExample.java index 393f788..67b4e65 100644 --- a/example/src/main/java/it/tdlight/example/AdvancedExample.java +++ b/example/src/main/java/it/tdlight/example/AdvancedExample.java @@ -2,7 +2,7 @@ package it.tdlight.example; import it.tdlight.Init; import it.tdlight.TelegramClient; -import it.tdlight.utils.CantLoadLibrary; +import it.tdlight.util.CantLoadLibrary; import it.tdlight.jni.TdApi; import it.tdlight.ClientFactory; diff --git a/example/src/main/java/it/tdlight/example/Example.java b/example/src/main/java/it/tdlight/example/Example.java index c2a7a1d..3eb9051 100644 --- a/example/src/main/java/it/tdlight/example/Example.java +++ b/example/src/main/java/it/tdlight/example/Example.java @@ -9,7 +9,7 @@ import it.tdlight.Init; import it.tdlight.jni.TdApi.AuthorizationState; import it.tdlight.jni.TdApi.Chat; import it.tdlight.jni.TdApi.MessageContent; -import it.tdlight.utils.CantLoadLibrary; +import it.tdlight.util.CantLoadLibrary; import it.tdlight.jni.TdApi; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/tdlight-java/src/main/java-templates/it/tdlight/utils/LibraryVersion.java b/tdlight-java/src/main/java-templates/it/tdlight/util/LibraryVersion.java similarity index 89% rename from tdlight-java/src/main/java-templates/it/tdlight/utils/LibraryVersion.java rename to tdlight-java/src/main/java-templates/it/tdlight/util/LibraryVersion.java index e612764..1183e32 100644 --- a/tdlight-java/src/main/java-templates/it/tdlight/utils/LibraryVersion.java +++ b/tdlight-java/src/main/java-templates/it/tdlight/util/LibraryVersion.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; public final class LibraryVersion { diff --git a/tdlight-java/src/main/java/it/tdlight/ArrayUtil.java b/tdlight-java/src/main/java/it/tdlight/ArrayUtil.java index 8e35aa8..d7f7fce 100644 --- a/tdlight-java/src/main/java/it/tdlight/ArrayUtil.java +++ b/tdlight-java/src/main/java/it/tdlight/ArrayUtil.java @@ -1,6 +1,6 @@ package it.tdlight; -import it.tdlight.utils.IntSwapper; +import it.tdlight.util.IntSwapper; class ArrayUtil { diff --git a/tdlight-java/src/main/java/it/tdlight/ClientFactory.java b/tdlight-java/src/main/java/it/tdlight/ClientFactory.java index bbe90bd..3a307d4 100644 --- a/tdlight-java/src/main/java/it/tdlight/ClientFactory.java +++ b/tdlight-java/src/main/java/it/tdlight/ClientFactory.java @@ -2,9 +2,9 @@ package it.tdlight; import it.tdlight.jni.TdApi; import it.tdlight.jni.TdApi.Object; -import it.tdlight.utils.CantLoadLibrary; -import it.tdlight.utils.CleanSupport; -import it.tdlight.utils.CleanSupport.CleanableSupport; +import it.tdlight.util.UnsupportedNativeLibraryException; +import it.tdlight.util.CleanSupport; +import it.tdlight.util.CleanSupport.CleanableSupport; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -52,8 +52,8 @@ public class ClientFactory implements AutoCloseable { public ClientFactory() { try { - Init.start(); - } catch (CantLoadLibrary e) { + Init.init(); + } catch (UnsupportedNativeLibraryException e) { throw new RuntimeException("Can't load the client factory because TDLight can't be loaded", e); } } @@ -69,7 +69,7 @@ public class ClientFactory implements AutoCloseable { public void startIfNeeded() { if (state.shouldStartNow()) { try { - Init.start(); + Init.init(); responseReceiver.start(); this.cleanable = CleanSupport.register(responseReceiver, () -> { try { diff --git a/tdlight-java/src/main/java/it/tdlight/ConstructorDetector.java b/tdlight-java/src/main/java/it/tdlight/ConstructorDetector.java index 9dc93ae..c26e693 100644 --- a/tdlight-java/src/main/java/it/tdlight/ConstructorDetector.java +++ b/tdlight-java/src/main/java/it/tdlight/ConstructorDetector.java @@ -23,7 +23,6 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; /** * Identify the class by using the Constructor. @@ -37,7 +36,7 @@ public final class ConstructorDetector { private static void tryInit() { // Call this to load static methods and prevent errors during startup! try { - Init.start(); + Init.init(); } catch (Throwable throwable) { throwable.printStackTrace(); } @@ -58,7 +57,7 @@ public final class ConstructorDetector { /** * Identify the class. * - * @param CONSTRUCTOR CONSTRUCTOR of the Tdlib API. + * @param CONSTRUCTOR CONSTRUCTOR of the TDLight API. * @return The class related to CONSTRUCTOR. */ public static Class getClass(int CONSTRUCTOR) { @@ -69,7 +68,7 @@ public final class ConstructorDetector { /** * Identify the class. * - * @param clazz class of the TDLib API. + * @param clazz class of the TDLight API. * @return The CONSTRUCTOR. */ public static int getConstructor(Class clazz) { diff --git a/tdlight-java/src/main/java/it/tdlight/Init.java b/tdlight-java/src/main/java/it/tdlight/Init.java index b45e838..6656d1f 100644 --- a/tdlight-java/src/main/java/it/tdlight/Init.java +++ b/tdlight-java/src/main/java/it/tdlight/Init.java @@ -17,8 +17,8 @@ package it.tdlight; -import it.tdlight.utils.CantLoadLibrary; -import it.tdlight.utils.LoadLibrary; +import it.tdlight.util.UnsupportedNativeLibraryException; +import it.tdlight.util.Native; import it.tdlight.jni.TdApi.LogStreamEmpty; import it.tdlight.jni.TdApi.SetLogStream; import it.tdlight.jni.TdApi.SetLogVerbosityLevel; @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Init class to successfully initialize Tdlib + * Initialize TDLight */ public final class Init { @@ -35,15 +35,16 @@ public final class Init { private static volatile boolean started = false; /** - * Initialize Tdlib + * Initialize TDLight. + * This method is idempotent. * - * @throws CantLoadLibrary An exception that is thrown when the LoadLibrary class fails to load the library. + * @throws UnsupportedNativeLibraryException An exception that is thrown when the LoadLibrary class fails to load the library. */ - public static void start() throws CantLoadLibrary { + public static void init() throws UnsupportedNativeLibraryException { if (!started) { synchronized (Init.class) { if (!started) { - LoadLibrary.load("tdjni"); + Native.loadNativesInternal(); ConstructorDetector.init(); try { NativeClientAccess.execute(new SetLogVerbosityLevel(3)); diff --git a/tdlight-java/src/main/java/it/tdlight/Log.java b/tdlight-java/src/main/java/it/tdlight/Log.java index 9baee5f..f0e8e81 100644 --- a/tdlight-java/src/main/java/it/tdlight/Log.java +++ b/tdlight-java/src/main/java/it/tdlight/Log.java @@ -15,7 +15,7 @@ public final class Log { static { try { - Init.start(); + Init.init(); } catch (Throwable throwable) { throwable.printStackTrace(); System.exit(0); diff --git a/tdlight-java/src/main/java/it/tdlight/ResponseReceiver.java b/tdlight-java/src/main/java/it/tdlight/ResponseReceiver.java index 49b6e31..3eb3ad2 100644 --- a/tdlight-java/src/main/java/it/tdlight/ResponseReceiver.java +++ b/tdlight-java/src/main/java/it/tdlight/ResponseReceiver.java @@ -3,8 +3,8 @@ package it.tdlight; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; -import it.tdlight.utils.IntSwapper; -import it.tdlight.utils.SpinWaitSupport; +import it.tdlight.util.IntSwapper; +import it.tdlight.util.SpinWaitSupport; import it.tdlight.jni.TdApi; import it.tdlight.jni.TdApi.UpdateAuthorizationState; import java.util.ArrayList; @@ -16,7 +16,6 @@ import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; abstract class ResponseReceiver extends Thread implements AutoCloseable { diff --git a/tdlight-java/src/main/java/it/tdlight/client/ConsoleInteractiveAuthenticationData.java b/tdlight-java/src/main/java/it/tdlight/client/ConsoleInteractiveAuthenticationData.java index 9db9a3e..a87c882 100644 --- a/tdlight-java/src/main/java/it/tdlight/client/ConsoleInteractiveAuthenticationData.java +++ b/tdlight-java/src/main/java/it/tdlight/client/ConsoleInteractiveAuthenticationData.java @@ -1,10 +1,9 @@ package it.tdlight.client; -import it.tdlight.utils.ScannerUtils; +import it.tdlight.util.ScannerUtils; import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; public final class ConsoleInteractiveAuthenticationData implements AuthenticationSupplier { diff --git a/tdlight-java/src/main/java/it/tdlight/client/ScannerClientInteraction.java b/tdlight-java/src/main/java/it/tdlight/client/ScannerClientInteraction.java index 0557088..3f35493 100644 --- a/tdlight-java/src/main/java/it/tdlight/client/ScannerClientInteraction.java +++ b/tdlight-java/src/main/java/it/tdlight/client/ScannerClientInteraction.java @@ -1,6 +1,6 @@ package it.tdlight.client; -import it.tdlight.utils.ScannerUtils; +import it.tdlight.util.ScannerUtils; import it.tdlight.jni.TdApi.TermsOfService; import java.util.Objects; import java.util.concurrent.CompletableFuture; diff --git a/tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClient.java b/tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClient.java index 47bf2ed..0c99b69 100644 --- a/tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClient.java +++ b/tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClient.java @@ -6,7 +6,7 @@ import it.tdlight.Init; import it.tdlight.ResultHandler; import it.tdlight.TelegramClient; import it.tdlight.jni.TdApi.Update; -import it.tdlight.utils.CantLoadLibrary; +import it.tdlight.util.UnsupportedNativeLibraryException; import it.tdlight.jni.TdApi; import it.tdlight.jni.TdApi.ChatListArchive; import it.tdlight.jni.TdApi.ChatListMain; @@ -21,9 +21,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; -import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +32,8 @@ public final class SimpleTelegramClient implements Authenticable, MutableTelegra static { try { - Init.start(); - } catch (CantLoadLibrary e) { + Init.init(); + } catch (UnsupportedNativeLibraryException e) { throw new RuntimeException("Can't load native libraries", e); } } diff --git a/tdlight-java/src/main/java/it/tdlight/client/TDLibSettings.java b/tdlight-java/src/main/java/it/tdlight/client/TDLibSettings.java index 7eceed1..dc0b44a 100644 --- a/tdlight-java/src/main/java/it/tdlight/client/TDLibSettings.java +++ b/tdlight-java/src/main/java/it/tdlight/client/TDLibSettings.java @@ -1,6 +1,6 @@ package it.tdlight.client; -import it.tdlight.utils.LibraryVersion; +import it.tdlight.util.LibraryVersion; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Locale; diff --git a/tdlight-java/src/main/java/it/tdlight/utils/CleanSupport.java b/tdlight-java/src/main/java/it/tdlight/util/CleanSupport.java similarity index 88% rename from tdlight-java/src/main/java/it/tdlight/utils/CleanSupport.java rename to tdlight-java/src/main/java/it/tdlight/util/CleanSupport.java index be702bb..3ebfed9 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/CleanSupport.java +++ b/tdlight-java/src/main/java/it/tdlight/util/CleanSupport.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; public class CleanSupport { diff --git a/tdlight-java/src/main/java/it/tdlight/utils/IntSwapper.java b/tdlight-java/src/main/java/it/tdlight/util/IntSwapper.java similarity index 90% rename from tdlight-java/src/main/java/it/tdlight/utils/IntSwapper.java rename to tdlight-java/src/main/java/it/tdlight/util/IntSwapper.java index 2962413..1b2d982 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/IntSwapper.java +++ b/tdlight-java/src/main/java/it/tdlight/util/IntSwapper.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; public final class IntSwapper { diff --git a/tdlight-java/src/main/java/it/tdlight/util/Native.java b/tdlight-java/src/main/java/it/tdlight/util/Native.java new file mode 100644 index 0000000..555daf0 --- /dev/null +++ b/tdlight-java/src/main/java/it/tdlight/util/Native.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018. Ernesto Castellotti + * This file is part of JTdlib. + * + * JTdlib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License. + * + * JTdlib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with JTdlib. If not, see . + */ +package it.tdlight.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.nio.ByteOrder; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * The class to load the libraries needed to run Tdlib + */ +public final class Native { + + /** + * Internal util + */ + public static void loadNativesInternal() throws UnsupportedNativeLibraryException { + loadLibrary("tdlight"); + } + + private static final Logger logger = LoggerFactory.getLogger(Native.class); + + /** + * Load a native library + * @param libraryName Library name + * @throws UnsupportedNativeLibraryException The library can't be loaded + */ + private static void loadLibrary(String libraryName) throws UnsupportedNativeLibraryException { + ClassLoader cl = Native.class.getClassLoader(); + String staticLibName = libraryName; + List sharedLibNames = getNormalizedArchitectures().map(suffix -> staticLibName + "." + suffix).collect(Collectors.toList()); + if (sharedLibNames.isEmpty()) { + throw new IllegalStateException(); + } + try { + NativeLibraryLoader.loadFirstAvailable(cl, sharedLibNames.toArray(new String[0])); + } catch (IllegalArgumentException | UnsatisfiedLinkError e1) { + try { + NativeLibraryLoader.load(staticLibName, cl); + logger.debug("Failed to load {}", String.join(", ", sharedLibNames), e1); + } catch (UnsatisfiedLinkError e2) { + e1.addSuppressed(e2); + throw new UnsupportedNativeLibraryException(e1); + } + } + } + + private static Stream getNormalizedArchitectures() { + String os = getOs(); + String arch = getCpuArch(); + if (os.equals("unknown") || arch.equals("unknown")) { + return getAllNormalizedArchitectures(); + } + return getNormalizedArchitectures(os, arch); + } + + private static Stream getAllNormalizedArchitectures() { + Set all = new LinkedHashSet<>(); + for (String os : new String[]{"windows"}) { + for (String arch : new String[]{"arm64", "amd64", "armhf", "i386", "s390x", "ppc64le"}) { + getNormalizedArchitectures(os, arch).forEach(all::add); + } + } + return all.stream(); + } + + private static Stream getNormalizedArchitectures(String os, String arch) { + switch (os) { + case "linux": { + return Stream.of("linux-" + arch + "-ssl1", "linux-" + arch + "-ssl3"); + } + case "windows": { + return Stream.of("windows-" + arch); + } + case "osx": { + return Stream.of("osx-" + arch); + } + default: { + throw new UnsupportedOperationException(); + } + } + } + + private static String getCpuArch() { + String architecture = System.getProperty("os.arch").trim(); + switch (architecture) { + case "amd64": + case "x86_64": + return "amd64"; + case "i386": + case "x86": + case "386": + case "i686": + case "686": + return "i386"; + case "armv6": + case "arm": + case "armhf": + case "aarch32": + case "armv7": + case "armv7l": + return "armhf"; + case "arm64": + case "aarch64": + case "armv8": + case "armv8l": + return "arm64"; + case "s390x": + return "s390x"; + case "powerpc": + case "powerpc64": + case "powerpc64le": + case "powerpc64el": + case "ppc": + case "ppc64": + case "ppc64le": + case "ppc64el": + if (ByteOrder + .nativeOrder() + .equals(ByteOrder.LITTLE_ENDIAN)) // Java always returns ppc64 for all 64-bit powerpc but + { + return "ppc64le"; // powerpc64le (our target) is very different, it uses this condition to accurately identify the architecture + } else { + return "unknown"; + } + default: + return "unknown"; + } + } + + public static String getOs() { + String os = System.getProperty("os.name").toLowerCase().trim(); + if (os.contains("linux")) { + return "linux"; + } + if (os.contains("windows")) { + return "windows"; + } + if (os.contains("mac")) { + return "osx"; + } + if (os.contains("darwin")) { + return "osx"; + } + return "unknown"; + } +} diff --git a/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryLoader.java b/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryLoader.java new file mode 100644 index 0000000..74c5549 --- /dev/null +++ b/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryLoader.java @@ -0,0 +1,598 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package it.tdlight.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Helper class to load JNI resources. + * + */ +public final class NativeLibraryLoader { + + private static final Logger logger = LoggerFactory.getLogger(NativeLibraryLoader.class); + + private static final String NATIVE_RESOURCE_HOME = "META-INF/tdlight-native/"; + private static final Path WORKDIR; + private static final boolean DELETE_NATIVE_LIB_AFTER_LOADING; + private static final boolean TRY_TO_PATCH_SHADED_ID; + private static final boolean DETECT_NATIVE_LIBRARY_DUPLICATES; + + // Just use a-Z and numbers as valid ID bytes. + private static final byte[] UNIQUE_ID_BYTES = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.US_ASCII); + + static { + String workdir = System.getProperty("it.tdlight.native.workdir"); + if (workdir != null) { + Path f = Paths.get(workdir); + try { + if (Files.notExists(f)) { + f = Files.createDirectories(f); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + f = f.toAbsolutePath(); + } catch (Exception ignored) { + // Good to have an absolute path, but it's OK. + } + + WORKDIR = f; + logger.debug("-Dit.tdlight.native.workdir: " + WORKDIR); + } else { + try { + WORKDIR = Files.createTempDirectory("tdlight-java-natives"); + } catch (IOException e) { + throw new RuntimeException(e); + } + logger.debug("-Dit.tdlight.native.workdir: " + WORKDIR + " (it.tdlight.tmpdir)"); + } + + DELETE_NATIVE_LIB_AFTER_LOADING = getBoolean( + "it.tdlight.native.deleteLibAfterLoading", true); + logger.debug("-Dit.tdlight.native.deleteLibAfterLoading: {}", DELETE_NATIVE_LIB_AFTER_LOADING); + + TRY_TO_PATCH_SHADED_ID = getBoolean( + "it.tdlight.native.tryPatchShadedId", true); + logger.debug("-Dit.tdlight.native.tryPatchShadedId: {}", TRY_TO_PATCH_SHADED_ID); + + DETECT_NATIVE_LIBRARY_DUPLICATES = getBoolean( + "it.tdlight.native.detectNativeLibraryDuplicates", true); + logger.debug("-Dit.tdlight.native.detectNativeLibraryDuplicates: {}", DETECT_NATIVE_LIBRARY_DUPLICATES); + } + + private static boolean getBoolean(String prop, boolean defaultValue) { + String value = System.getProperty(prop); + return value == null ? defaultValue : Boolean.parseBoolean(value); + } + + /** + * Loads the first available library in the collection with the specified + * {@link ClassLoader}. + * + * @throws IllegalArgumentException + * if none of the given libraries load successfully. + */ + public static void loadFirstAvailable(ClassLoader loader, String... names) { + List suppressed = new ArrayList(); + for (String name : names) { + try { + load(name, loader); + logger.debug("Loaded library with name '{}'", name); + return; + } catch (Throwable t) { + suppressed.add(t); + } + } + + IllegalArgumentException iae = + new IllegalArgumentException("Failed to load any of the given libraries: " + Arrays.toString(names)); + addSuppressedAndClear(iae, suppressed); + throw iae; + } + + /** + * Calculates the mangled shading prefix added to this class's full name. + * + *

This method mangles the package name as follows, so we can unmangle it back later: + *

    + *
  • {@code _} to {@code _1}
  • + *
  • {@code .} to {@code _}
  • + *
+ * + *

Note that we don't mangle non-ASCII characters here because it's extremely unlikely to have + * a non-ASCII character in a package name. For more information, see: + *

+ * + * @throws UnsatisfiedLinkError if the shader used something other than a prefix + */ + private static String calculateMangledPackagePrefix() { + String maybeShaded = NativeLibraryLoader.class.getName(); + // Use ! instead of . to avoid shading utilities from modifying the string + String expected = "it!tdlight!util!internal!NativeLibraryLoader".replace('!', '.'); + if (!maybeShaded.endsWith(expected)) { + throw new UnsatisfiedLinkError(String.format( + "Could not find prefix added to %s to get %s. When shading, only adding a " + + "package prefix is supported", expected, maybeShaded)); + } + return maybeShaded.substring(0, maybeShaded.length() - expected.length()) + .replace("_", "_1") + .replace('.', '_'); + } + + /** + * Load the given library with the specified {@link ClassLoader} + */ + public static void load(String originalName, ClassLoader loader) { + String mangledPackagePrefix = calculateMangledPackagePrefix(); + String name = mangledPackagePrefix + originalName; + List suppressed = new ArrayList(); + try { + // first try to load from java.library.path + loadLibrary(loader, name, false); + return; + } catch (Throwable ex) { + suppressed.add(ex); + } + + String libname = System.mapLibraryName(name); + String path = NATIVE_RESOURCE_HOME + libname; + + InputStream in = null; + OutputStream out = null; + Path tmpFile = null; + URL url = getResource(path, loader); + try { + if (url == null) { + if (isOsx()) { + String fileName = path.endsWith(".jnilib") ? NATIVE_RESOURCE_HOME + "lib" + name + ".dylib" : + NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib"; + url = getResource(fileName, loader); + if (url == null) { + FileNotFoundException fnf = new FileNotFoundException(fileName); + addSuppressedAndClear(fnf, suppressed); + throw fnf; + } + } else { + FileNotFoundException fnf = new FileNotFoundException(path); + addSuppressedAndClear(fnf, suppressed); + throw fnf; + } + } + + int index = libname.lastIndexOf('.'); + String prefix = libname.substring(0, index); + String suffix = libname.substring(index); + + tmpFile = createTempFile(prefix, suffix, WORKDIR); + in = url.openStream(); + out = Files.newOutputStream(tmpFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + byte[] buffer = new byte[8192]; + int length; + while ((length = in.read(buffer)) > 0) { + out.write(buffer, 0, length); + } + out.flush(); + + if (shouldShadedLibraryIdBePatched(mangledPackagePrefix)) { + // Let's try to patch the id and re-sign it. This is a best-effort and might fail if a + // SecurityManager is setup or the right executables are not installed :/ + tryPatchShadedLibraryIdAndSign(tmpFile, originalName); + } + + // Close the output stream before loading the unpacked library, + // because otherwise Windows will refuse to load it when it's in use by other process. + closeQuietly(out); + out = null; + + loadLibrary(loader, tmpFile.toString(), true); + } catch (UnsatisfiedLinkError e) { + try { + if (tmpFile != null && Files.isRegularFile(tmpFile) && Files.isReadable(tmpFile) && + !NoexecVolumeDetector.canExecuteExecutable(tmpFile)) { + // Pass "it.tdlight.native.workdir" as an argument to allow shading tools to see + // the string. Since this is printed out to users to tell them what to do next, + // we want the value to be correct even when shading. + logger.info("{} exists but cannot be executed even when execute permissions set; " + + "check volume for \"noexec\" flag; use -D{}=[path] " + + "to set native working directory separately.", + tmpFile, "it.tdlight.native.workdir"); + } + } catch (Throwable t) { + suppressed.add(t); + logger.debug("Error checking if {} is on a file store mounted with noexec", tmpFile, t); + } + // Re-throw to fail the load + addSuppressedAndClear(e, suppressed); + throw e; + } catch (Exception e) { + UnsatisfiedLinkError ule = new UnsatisfiedLinkError("could not load a native library: " + name); + ule.initCause(e); + addSuppressedAndClear(ule, suppressed); + throw ule; + } finally { + closeQuietly(in); + closeQuietly(out); + // After we load the library it is safe to delete the file. + // We delete the file immediately to free up resources as soon as possible, + // and if this fails fallback to deleting on JVM exit. + try { + if (tmpFile != null && (!DELETE_NATIVE_LIB_AFTER_LOADING || !Files.deleteIfExists(tmpFile))) { + tmpFile.toFile().deleteOnExit(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static boolean isOsx() { + return Native.getOs().equals("osx"); + } + + private static Path createTempFile(String prefix, String suffix, Path workdir) { + try { + return Files.createTempFile(workdir, prefix, suffix); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void addSuppressedAndClear(Throwable ex, List suppressed) { + suppressed.forEach(ex::addSuppressed); + suppressed.clear(); + } + + private static URL getResource(String path, ClassLoader loader) { + final Enumeration urls; + try { + if (loader == null) { + urls = ClassLoader.getSystemResources(path); + } else { + urls = loader.getResources(path); + } + } catch (IOException iox) { + throw new RuntimeException("An error occurred while getting the resources for " + path, iox); + } + + List urlsList = Collections.list(urls); + int size = urlsList.size(); + switch (size) { + case 0: + return null; + case 1: + return urlsList.get(0); + default: + if (DETECT_NATIVE_LIBRARY_DUPLICATES) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + // We found more than 1 resource with the same name. Let's check if the content of the file is + // the same as in this case it will not have any bad effect. + URL url = urlsList.get(0); + byte[] digest = digest(md, url); + boolean allSame = true; + if (digest != null) { + for (int i = 1; i < size; i++) { + byte[] digest2 = digest(md, urlsList.get(i)); + if (digest2 == null || !Arrays.equals(digest, digest2)) { + allSame = false; + break; + } + } + } else { + allSame = false; + } + if (allSame) { + return url; + } + } catch (NoSuchAlgorithmException e) { + logger.debug("Don't support SHA-256, can't check if resources have same content.", e); + } + + throw new IllegalStateException( + "Multiple resources found for '" + path + "' with different content: " + urlsList); + } else { + logger.warn("Multiple resources found for '" + path + "' with different content: " + + urlsList + ". Please fix your dependency graph."); + return urlsList.get(0); + } + } + } + + private static byte[] digest(MessageDigest digest, URL url) { + InputStream in = null; + try { + in = url.openStream(); + byte[] bytes = new byte[8192]; + int i; + while ((i = in.read(bytes)) != -1) { + digest.update(bytes, 0, i); + } + return digest.digest(); + } catch (IOException e) { + logger.debug("Can't read resource.", e); + return null; + } finally { + closeQuietly(in); + } + } + + static void tryPatchShadedLibraryIdAndSign(Path libraryFile, String originalName) { + if (Files.notExists(Paths.get("/Library/Developer/CommandLineTools"))) { + logger.debug("Can't patch shaded library id as CommandLineTools are not installed." + + " Consider installing CommandLineTools with 'xcode-select --install'"); + return; + } + String newId = new String(generateUniqueId(originalName.length()), StandardCharsets.UTF_8); + if (!tryExec("install_name_tool -id " + newId + " " + libraryFile.toAbsolutePath())) { + return; + } + + tryExec("codesign -s - " + libraryFile.toAbsolutePath()); + } + + private static boolean tryExec(String cmd) { + try { + int exitValue = Runtime.getRuntime().exec(cmd).waitFor(); + if (exitValue != 0) { + logger.debug("Execution of '{}' failed: {}", cmd, exitValue); + return false; + } + logger.debug("Execution of '{}' succeed: {}", cmd, exitValue); + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (IOException e) { + logger.info("Execution of '{}' failed.", cmd, e); + } catch (SecurityException e) { + logger.error("Execution of '{}' failed.", cmd, e); + } + return false; + } + + private static boolean shouldShadedLibraryIdBePatched(String packagePrefix) { + return TRY_TO_PATCH_SHADED_ID && isOsx() && !packagePrefix.isEmpty(); + } + + private static byte[] generateUniqueId(int length) { + byte[] idBytes = new byte[length]; + for (int i = 0; i < idBytes.length; i++) { + // We should only use bytes as replacement that are in our UNIQUE_ID_BYTES array. + idBytes[i] = UNIQUE_ID_BYTES[ThreadLocalRandom.current() + .nextInt(UNIQUE_ID_BYTES.length)]; + } + return idBytes; + } + + /** + * Loading the native library into the specified {@link ClassLoader}. + * @param loader - The {@link ClassLoader} where the native library will be loaded into + * @param name - The native library path or name + * @param absolute - Whether the native library will be loaded by path or by name + */ + private static void loadLibrary(final ClassLoader loader, final String name, final boolean absolute) { + Throwable suppressed = null; + try { + try { + // Make sure the helper belongs to the target ClassLoader. + final Class newHelper = tryToLoadClass(loader, NativeLibraryUtil.class); + loadLibraryByHelper(newHelper, name, absolute); + logger.debug("Successfully loaded the library {}", name); + return; + } catch (UnsatisfiedLinkError e) { // Should by pass the UnsatisfiedLinkError here! + suppressed = e; + } catch (Exception e) { + suppressed = e; + } + NativeLibraryUtil.loadLibrary(name, absolute); // Fallback to local helper class. + logger.debug("Successfully loaded the library {}", name); + } catch (NoSuchMethodError nsme) { + if (suppressed != null) { + nsme.addSuppressed(suppressed); + } + rethrowWithMoreDetailsIfPossible(name, nsme); + } catch (UnsatisfiedLinkError ule) { + if (suppressed != null) { + ule.addSuppressed(suppressed); + } + throw ule; + } + } + + private static void rethrowWithMoreDetailsIfPossible(String name, NoSuchMethodError error) { + throw new LinkageError( + "Possible multiple incompatible native libraries on the classpath for '" + name + "'?", error); + } + + private static void loadLibraryByHelper(final Class helper, final String name, final boolean absolute) + throws UnsatisfiedLinkError { + + Object ret; + try { + // Invoke the helper to load the native library, if succeed, then the native + // library belong to the specified ClassLoader. + Method method = helper.getMethod("loadLibrary", String.class, boolean.class); + method.setAccessible(true); + ret = method.invoke(null, name, absolute); + } catch (Exception e) { + ret = e; + } + if (ret instanceof Throwable) { + Throwable t = (Throwable) ret; + assert !(t instanceof UnsatisfiedLinkError) : t + " should be a wrapper throwable"; + Throwable cause = t.getCause(); + if (cause instanceof UnsatisfiedLinkError) { + throw (UnsatisfiedLinkError) cause; + } + UnsatisfiedLinkError ule = new UnsatisfiedLinkError(t.getMessage()); + ule.initCause(t); + throw ule; + } + } + + /** + * Try to load the helper {@link Class} into specified {@link ClassLoader}. + * @param loader - The {@link ClassLoader} where to load the helper {@link Class} + * @param helper - The helper {@link Class} + * @return A new helper Class defined in the specified ClassLoader. + * @throws ClassNotFoundException Helper class not found or loading failed + */ + private static Class tryToLoadClass(final ClassLoader loader, final Class helper) + throws ClassNotFoundException { + try { + return Class.forName(helper.getName(), false, loader); + } catch (ClassNotFoundException e1) { + if (loader == null) { + // cannot defineClass inside bootstrap class loader + throw e1; + } + try { + // The helper class is NOT found in target ClassLoader, we have to define the helper class. + final byte[] classBinary = classToByteArray(helper); + + try { + // Define the helper class in the target ClassLoader, + // then we can call the helper to load the native library. + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, + byte[].class, int.class, int.class); + defineClass.setAccessible(true); + return (Class) defineClass.invoke(loader, helper.getName(), classBinary, 0, + classBinary.length); + } catch (Exception e) { + throw new IllegalStateException("Define class failed!", e); + } + } catch (ClassNotFoundException | RuntimeException | Error e2) { + e2.addSuppressed(e1); + throw e2; + } + } + } + + /** + * Load the helper {@link Class} as a byte array, to be redefined in specified {@link ClassLoader}. + * @param clazz - The helper {@link Class} provided by this bundle + * @return The binary content of helper {@link Class}. + * @throws ClassNotFoundException Helper class not found or loading failed + */ + private static byte[] classToByteArray(Class clazz) throws ClassNotFoundException { + String fileName = clazz.getName(); + int lastDot = fileName.lastIndexOf('.'); + if (lastDot > 0) { + fileName = fileName.substring(lastDot + 1); + } + URL classUrl = clazz.getResource(fileName + ".class"); + if (classUrl == null) { + throw new ClassNotFoundException(clazz.getName()); + } + byte[] buf = new byte[1024]; + ByteArrayOutputStream out = new ByteArrayOutputStream(4096); + InputStream in = null; + try { + in = classUrl.openStream(); + for (int r; (r = in.read(buf)) != -1;) { + out.write(buf, 0, r); + } + return out.toByteArray(); + } catch (IOException ex) { + throw new ClassNotFoundException(clazz.getName(), ex); + } finally { + closeQuietly(in); + closeQuietly(out); + } + } + + private static void closeQuietly(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException ignore) { + // ignore + } + } + } + + private NativeLibraryLoader() { + // Utility + } + + private static final class NoexecVolumeDetector { + + private static boolean canExecuteExecutable(Path file) throws IOException { + // If we can already execute, there is nothing to do. + if (Files.isExecutable(file)) { + return true; + } + + // On volumes, with noexec set, even files with the executable POSIX permissions will fail to execute. + // The File#canExecute() method honors this behavior, probaby via parsing the noexec flag when initializing + // the UnixFileStore, though the flag is not exposed via a public API. To find out if library is being + // loaded off a volume with noexec, confirm or add executalbe permissions, then check File#canExecute(). + + // Note: We use FQCN to not break when netty is used in java6 + Set existingFilePermissions = + java.nio.file.Files.getPosixFilePermissions(file); + Set executePermissions = + EnumSet.of(java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE, + java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE, + java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE); + if (existingFilePermissions.containsAll(executePermissions)) { + return false; + } + + Set newPermissions = EnumSet.copyOf(existingFilePermissions); + newPermissions.addAll(executePermissions); + java.nio.file.Files.setPosixFilePermissions(file, newPermissions); + return Files.isExecutable(file); + } + + private NoexecVolumeDetector() { + // Utility + } + } +} \ No newline at end of file diff --git a/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryUtil.java b/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryUtil.java new file mode 100644 index 0000000..d5329ca --- /dev/null +++ b/tdlight-java/src/main/java/it/tdlight/util/NativeLibraryUtil.java @@ -0,0 +1,30 @@ +package it.tdlight.util; + +/** + * A Utility to Call the {@link System#load(String)} or {@link System#loadLibrary(String)}. + * Because the {@link System#load(String)} and {@link System#loadLibrary(String)} are both + * CallerSensitive, it will load the native library into its caller's {@link ClassLoader}. + * In OSGi environment, we need this helper to delegate the calling to {@link System#load(String)} + * and it should be as simple as possible. It will be injected into the native library's + * ClassLoader when it is undefined. And therefore, when the defined new helper is invoked, + * the native library would be loaded into the native library's ClassLoader, not the + * caller's ClassLoader. + */ +final class NativeLibraryUtil { + /** + * Delegate the calling to {@link System#load(String)} or {@link System#loadLibrary(String)}. + * @param libName - The native library path or name + * @param absolute - Whether the native library will be loaded by path or by name + */ + public static void loadLibrary(String libName, boolean absolute) { + if (absolute) { + System.load(libName); + } else { + System.loadLibrary(libName); + } + } + + private NativeLibraryUtil() { + // Utility + } +} \ No newline at end of file diff --git a/tdlight-java/src/main/java/it/tdlight/utils/ScannerUtils.java b/tdlight-java/src/main/java/it/tdlight/util/ScannerUtils.java similarity index 98% rename from tdlight-java/src/main/java/it/tdlight/utils/ScannerUtils.java rename to tdlight-java/src/main/java/it/tdlight/util/ScannerUtils.java index 15e9d35..56bab31 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/ScannerUtils.java +++ b/tdlight-java/src/main/java/it/tdlight/util/ScannerUtils.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; import java.io.Console; import java.io.IOException; diff --git a/tdlight-java/src/main/java/it/tdlight/utils/SpinWaitSupport.java b/tdlight-java/src/main/java/it/tdlight/util/SpinWaitSupport.java similarity index 89% rename from tdlight-java/src/main/java/it/tdlight/utils/SpinWaitSupport.java rename to tdlight-java/src/main/java/it/tdlight/util/SpinWaitSupport.java index 8ee7bd8..2e91c66 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/SpinWaitSupport.java +++ b/tdlight-java/src/main/java/it/tdlight/util/SpinWaitSupport.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; import java.util.concurrent.locks.LockSupport; diff --git a/tdlight-java/src/main/java/it/tdlight/utils/TDLightBlockHoundIntegration.java b/tdlight-java/src/main/java/it/tdlight/util/TDLightBlockHoundIntegration.java similarity index 94% rename from tdlight-java/src/main/java/it/tdlight/utils/TDLightBlockHoundIntegration.java rename to tdlight-java/src/main/java/it/tdlight/util/TDLightBlockHoundIntegration.java index 1196f99..ebbf614 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/TDLightBlockHoundIntegration.java +++ b/tdlight-java/src/main/java/it/tdlight/util/TDLightBlockHoundIntegration.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; import reactor.blockhound.BlockHound.Builder; diff --git a/tdlight-java/src/main/java/it/tdlight/utils/CantLoadLibrary.java b/tdlight-java/src/main/java/it/tdlight/util/UnsupportedNativeLibraryException.java similarity index 59% rename from tdlight-java/src/main/java/it/tdlight/utils/CantLoadLibrary.java rename to tdlight-java/src/main/java/it/tdlight/util/UnsupportedNativeLibraryException.java index dcd1f77..f55b228 100644 --- a/tdlight-java/src/main/java/it/tdlight/utils/CantLoadLibrary.java +++ b/tdlight-java/src/main/java/it/tdlight/util/UnsupportedNativeLibraryException.java @@ -15,29 +15,29 @@ * along with JTdlib. If not, see . */ -package it.tdlight.utils; +package it.tdlight.util; /** - * An exception that is thrown when the LoadLibrary class fails to load the library. + * An exception that is thrown when a native library can't be loaded. */ -public final class CantLoadLibrary extends Exception { +public final class UnsupportedNativeLibraryException extends Exception { /** - * Creates a new CantLoadLibrary exception. + * Creates a new UnsupportedNativeLibraryException. */ - CantLoadLibrary() { - super("Init failed when loading TDLib native libraries, execution can't continue"); + UnsupportedNativeLibraryException() { + super("Failed to load TDLight native libraries"); } - public CantLoadLibrary(String message) { + public UnsupportedNativeLibraryException(String message) { super(message); } - public CantLoadLibrary(String message, Exception cause) { + public UnsupportedNativeLibraryException(String message, Exception cause) { super(message, cause); } - public CantLoadLibrary(Throwable cause) { - super(cause); + public UnsupportedNativeLibraryException(Throwable cause) { + super("Failed to load TDLight native libraries", cause); } } diff --git a/tdlight-java/src/main/java/it/tdlight/utils/LoadLibrary.java b/tdlight-java/src/main/java/it/tdlight/utils/LoadLibrary.java deleted file mode 100644 index 7948e0b..0000000 --- a/tdlight-java/src/main/java/it/tdlight/utils/LoadLibrary.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (c) 2018. Ernesto Castellotti - * This file is part of JTdlib. - * - * JTdlib is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License. - * - * JTdlib is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with JTdlib. If not, see . - */ -package it.tdlight.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * The class to load the libraries needed to run Tdlib - */ -public final class LoadLibrary { - - private static final Set LIBRARY_LOADED = new ConcurrentHashMap().keySet(true); - private static final String LIBS_VERSION = - LibraryVersion.IMPLEMENTATION_NAME + "-" + LibraryVersion.VERSION + "-" + LibraryVersion.NATIVES_VERSION; - - /** - * Load a library installed in the system (priority choice) or a library included in the jar. - * - * @param libname The name of the library. - * @throws CantLoadLibrary An exception that is thrown when the LoadLibrary class fails to load the library. - */ - public static void load(String libname) throws CantLoadLibrary { - if (libname == null || libname.trim().isEmpty()) { - throw new IllegalArgumentException(); - } - - if (LIBRARY_LOADED.contains(libname)) return; - synchronized (LoadLibrary.class) { - if (LIBRARY_LOADED.contains(libname)) return; - - String libraryCachePathString = System.getProperty("it.tdlight.libraryCachePath"); - Path libraryCachePath = libraryCachePathString != null ? Paths.get(libraryCachePathString) : null; - loadLibrary(libname, libraryCachePath); - LIBRARY_LOADED.add(libname); - } - } - - /** - * Load a native library - * @param libraryName Library name - * @param libraryCachePath optional, path in which the library will be extracted - * @throws CantLoadLibrary The library can't be loaded - */ - private static void loadLibrary(String libraryName, Path libraryCachePath) throws CantLoadLibrary { - if (libraryCachePath == null) { - libraryCachePath = Paths.get(System.getProperty("user.home")).resolve(".cache").resolve("tdlight-jni-cache"); - } - try { - loadJarLibrary(libraryName, libraryCachePath); - } catch (CantLoadLibrary | UnsatisfiedLinkError e) { - if (loadSysLibrary(libraryName)) { - return; - } - throw new CantLoadLibrary(e); - } - } - - private static boolean loadSysLibrary(String libname) { - try { - System.loadLibrary(libname); - } catch (UnsatisfiedLinkError e) { - return false; - } - - return true; - } - - private static void loadJarLibrary(String libraryName, Path libraryCachePath) throws CantLoadLibrary { - Path tempPath; - try { - tempPath = libraryCachePath.resolve("version-" + LIBS_VERSION).resolve(libraryName); - if (Files.notExists(tempPath)) { - tempPath = Files.createDirectories(tempPath); - } - } catch (IOException e) { - throw new CantLoadLibrary("Can't create temporary files", e); - } - - ClassLoader classForResource = LoadLibrary.class.getClassLoader(); - List normalizedArchs = getNormalizedArchitectures().collect(Collectors.toList()); - Exception lastEx = null; - loadAny: for (String normalizedArch : normalizedArchs) { - Path tempFile = tempPath.resolve(libraryName + "." + normalizedArch); - InputStream libInputStream; - try { - libInputStream = Objects.requireNonNull(classForResource.getResourceAsStream("META-INF/tdlight-jni/lib" + libraryName + "." + normalizedArch)); - if (Files.notExists(tempFile)) { - try { - Files.copy(libInputStream, tempFile); - } catch (IOException e) { - throw new CantLoadLibrary("Can't copy native libraries into temporary files", e); - } - } - try { - libInputStream.close(); - } catch (IOException e) { - throw new CantLoadLibrary("Can't load the native libraries", e); - } - System.load(tempFile.toAbsolutePath().toString()); - lastEx = null; - break loadAny; - } catch (Throwable e) { - lastEx = new CantLoadLibrary(e); - } - } - if (lastEx != null) { - throw new CantLoadLibrary("Native libraries for platforms " - + String.join(", ", normalizedArchs) + " not found!", lastEx); - } - } - - private static Stream getNormalizedArchitectures() { - String os = getOs(); - String arch = getCpuArch(); - if (os.equals("unknown") || arch.equals("unknown")) { - return getAllNormalizedArchitectures(); - } - return getNormalizedArchitectures(os, arch); - } - - private static Stream getAllNormalizedArchitectures() { - Set all = new LinkedHashSet<>(); - for (String os : new String[]{"windows"}) { - for (String arch : new String[]{"arm64", "amd64", "armhf", "i386", "s390x", "ppc64le"}) { - getNormalizedArchitectures(os, arch).forEach(all::add); - } - } - return all.stream(); - } - - private static Stream getNormalizedArchitectures(String os, String arch) { - switch (os) { - case "linux": { - return Stream.of("linux-" + arch + "-ssl1.so", "linux-" + arch + "-ssl3.so"); - } - case "windows": { - return Stream.of("windows-" + arch + ".dll"); - } - case "osx": { - return Stream.of("osx-" + arch + ".dylib"); - } - default: { - throw new UnsupportedOperationException(); - } - } - } - - private static String getCpuArch() { - String architecture = System.getProperty("os.arch").trim(); - switch (architecture) { - case "amd64": - case "x86_64": - return "amd64"; - case "i386": - case "x86": - case "386": - case "i686": - case "686": - return "i386"; - case "armv6": - case "arm": - case "armhf": - case "aarch32": - case "armv7": - case "armv7l": - return "armhf"; - case "arm64": - case "aarch64": - case "armv8": - case "armv8l": - return "arm64"; - case "s390x": - return "s390x"; - case "powerpc": - case "powerpc64": - case "powerpc64le": - case "powerpc64el": - case "ppc": - case "ppc64": - case "ppc64le": - case "ppc64el": - if (ByteOrder - .nativeOrder() - .equals(ByteOrder.LITTLE_ENDIAN)) // Java always returns ppc64 for all 64-bit powerpc but - { - return "ppc64le"; // powerpc64le (our target) is very different, it uses this condition to accurately identify the architecture - } else { - return "unknown"; - } - default: - return "unknown"; - } - } - - public static String getOs() { - String os = System.getProperty("os.name").toLowerCase().trim(); - if (os.contains("linux")) { - return "linux"; - } - if (os.contains("windows")) { - return "windows"; - } - if (os.contains("mac")) { - return "osx"; - } - if (os.contains("darwin")) { - return "osx"; - } - return "unknown"; - } -} diff --git a/tdlight-java/src/main/java/module-info.java b/tdlight-java/src/main/java/module-info.java index c5ecf31..24eaf84 100644 --- a/tdlight-java/src/main/java/module-info.java +++ b/tdlight-java/src/main/java/module-info.java @@ -6,6 +6,6 @@ module tdlight.java { requires static reactor.blockhound; exports it.tdlight.tdnative; exports it.tdlight; - exports it.tdlight.utils; + exports it.tdlight.util; exports it.tdlight.client; } diff --git a/tdlight-java/src/main/java11/it/tdlight/utils/CleanSupport.java b/tdlight-java/src/main/java11/it/tdlight/util/CleanSupport.java similarity index 92% rename from tdlight-java/src/main/java11/it/tdlight/utils/CleanSupport.java rename to tdlight-java/src/main/java11/it/tdlight/util/CleanSupport.java index d446f57..1b0bc9d 100644 --- a/tdlight-java/src/main/java11/it/tdlight/utils/CleanSupport.java +++ b/tdlight-java/src/main/java11/it/tdlight/util/CleanSupport.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; import java.lang.ref.Cleaner; diff --git a/tdlight-java/src/main/java11/it/tdlight/utils/SpinWaitSupport.java b/tdlight-java/src/main/java11/it/tdlight/util/SpinWaitSupport.java similarity index 84% rename from tdlight-java/src/main/java11/it/tdlight/utils/SpinWaitSupport.java rename to tdlight-java/src/main/java11/it/tdlight/util/SpinWaitSupport.java index 21e1b3b..42c0a31 100644 --- a/tdlight-java/src/main/java11/it/tdlight/utils/SpinWaitSupport.java +++ b/tdlight-java/src/main/java11/it/tdlight/util/SpinWaitSupport.java @@ -1,4 +1,4 @@ -package it.tdlight.utils; +package it.tdlight.util; public class SpinWaitSupport { diff --git a/tdlight-java/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/tdlight-java/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration index 9fcb694..5f4590f 100644 --- a/tdlight-java/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration +++ b/tdlight-java/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration @@ -1 +1 @@ -it.tdlight.utils.TDLightBlockHoundIntegration +it.tdlight.util.TDLightBlockHoundIntegration