NativeCodeLoader.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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
*
* http://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 org.apache.commons.crypto;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFileAttributes;
import java.util.Objects;
import java.util.Properties;
import java.util.UUID;
import org.apache.commons.crypto.utils.Utils;
/**
* A helper to load the native code i.e. libcommons-crypto.so. This handles the
* fallback to either the bundled libcommons-crypto-Linux-i386-32.so or the
* default java implementations where appropriate.
*/
final class NativeCodeLoader {
private static final String SIMPLE_NAME = NativeCodeLoader.class.getSimpleName();
private static final String NATIVE_LIBNAME = "commons-crypto";
private static final String NATIVE_LIBNAME_ALT = "lib" + NATIVE_LIBNAME + ".jnilib";
/**
* End of file pseudo-character.
*/
private static final int EOF = -1;
private static final Throwable libraryLoadingError;
private static final boolean libraryLoaded;
static {
debug("%s static init start", SIMPLE_NAME);
libraryLoadingError = loadLibrary(); // will be null if loaded OK
libraryLoaded = libraryLoadingError == null;
debug("%s libraryLoaded = %s, libraryLoadingError = %s", SIMPLE_NAME, libraryLoaded, libraryLoadingError);
debug("%s static init end", SIMPLE_NAME);
}
/**
* Returns the given InputStream if it is already a {@link BufferedInputStream},
* otherwise creates a BufferedInputStream from the given InputStream.
* <p>
* Copied from Apache Commons IO 2.5.
* </p>
*
* @param inputStream the InputStream to wrap or return (not null)
* @return the given InputStream or a new {@link BufferedInputStream} for the
* given InputStream
* @throws NullPointerException if the input parameter is null
*/
@SuppressWarnings("resource")
private static BufferedInputStream buffer(final InputStream inputStream) {
// reject null early on rather than waiting for IO operation to fail
// not checked by BufferedInputStream
Objects.requireNonNull(inputStream, "inputStream");
return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream
: new BufferedInputStream(inputStream);
}
/**
* Checks whether in1 and in2 is equal.
* <p>
* Copied from Apache Commons IO 2.5.
* </p>
*
* @param input1 the input1.
* @param input2 the input2.
* @return true if in1 and in2 is equal, else false.
* @throws IOException if an I/O error occurs.
*/
@SuppressWarnings("resource")
private static boolean contentsEquals(final InputStream input1, final InputStream input2) throws IOException {
if (input1 == input2) {
return true;
}
if (input1 == null ^ input2 == null) {
return false;
}
final BufferedInputStream bufferedInput1 = buffer(input1);
final BufferedInputStream bufferedInput2 = buffer(input2);
int ch = bufferedInput1.read();
while (EOF != ch) {
final int ch2 = bufferedInput2.read();
if (ch != ch2) {
return false;
}
ch = bufferedInput1.read();
}
return bufferedInput2.read() == EOF;
}
/**
* Logs debug messages.
*
* @param format See {@link String#format(String, Object...)}.
* @param args See {@link String#format(String, Object...)}.
*/
private static void debug(final String format, final Object... args) {
// TODO Find a better way to do this later.
if (isDebug()) {
System.out.println(String.format(format, args));
if (args != null && args.length > 0 && args[0] instanceof Throwable) {
((Throwable) args[0]).printStackTrace(System.out);
}
}
}
/**
* Extracts the specified library file to the target folder.
*
* @param libFolderForCurrentOS the library in commons-crypto.lib.path.
* @param libraryFileName the library name.
* @param targetFolder Target folder for the native lib. Use the value
* of commons-crypto.tempdir or java.io.tmpdir.
* @return the library file.
*/
private static File extractLibraryFile(final String libFolderForCurrentOS, final String libraryFileName,
final String targetFolder) {
final String nativeLibraryFilePath = libFolderForCurrentOS + File.separator + libraryFileName;
// Attach UUID to the native library file to ensure multiple class loaders
// can read the libcommons-crypto multiple times.
final UUID uuid = UUID.randomUUID();
final String extractedLibFileName = String.format("commons-crypto-%s-%s", uuid, libraryFileName);
final File extractedLibFile = new File(targetFolder, extractedLibFileName);
debug("Extracting '%s' to '%s'...", nativeLibraryFilePath, extractedLibFile);
try (InputStream inputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
if (inputStream == null) {
debug("Resource not found: %s", nativeLibraryFilePath);
return null;
}
// Extract a native library file into the target directory
final Path path;
try {
path = extractedLibFile.toPath();
final long byteCount = Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
if (isDebug()) {
debug("Extracted '%s' to '%s': %,d bytes [%s]", nativeLibraryFilePath, extractedLibFile, byteCount,
Files.isExecutable(path) ? "X+" : "X-");
final PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class);
if (attributes != null) {
debug("Attributes '%s': %s %s %s", extractedLibFile, attributes.permissions(),
attributes.owner(), attributes.group());
}
}
} finally {
// Delete the extracted lib file on JVM exit.
debug("Delete on exit: %s", extractedLibFile);
extractedLibFile.deleteOnExit();
}
// Set executable (x) flag to enable Java to load the native library
if (!extractedLibFile.setReadable(true) || !extractedLibFile.setExecutable(true)
|| !extractedLibFile.setWritable(true, true)) {
throw new IllegalStateException("Invalid path for library path " + extractedLibFile);
}
// Check whether the contents are properly copied from the resource
// folder
try (InputStream nativeInputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
try (InputStream extractedLibIn = Files.newInputStream(path)) {
debug("Validating '%s'...", extractedLibFile);
if (!contentsEquals(nativeInputStream, extractedLibIn)) {
throw new IllegalStateException(String.format("Failed to write a native library file %s to %s",
nativeLibraryFilePath, extractedLibFile));
}
}
}
return extractedLibFile;
} catch (final IOException e) {
debug("Ignoring %s", e);
return null;
}
}
/**
* Finds the native library.
*
* @return the jar file.
*/
private static File findNativeLibrary() {
// Get the properties once
final Properties props = Utils.getDefaultProperties();
// Try to load the library in commons-crypto.lib.path */
String nativeLibraryPath = props.getProperty(Crypto.LIB_PATH_KEY);
String nativeLibraryName = props.getProperty(Crypto.LIB_NAME_KEY, System.mapLibraryName(NATIVE_LIBNAME));
debug("%s nativeLibraryPath %s = %s", SIMPLE_NAME, Crypto.LIB_PATH_KEY, nativeLibraryPath);
debug("%s nativeLibraryName %s = %s", SIMPLE_NAME, Crypto.LIB_NAME_KEY, nativeLibraryName);
if (nativeLibraryPath != null) {
final File nativeLib = new File(nativeLibraryPath, nativeLibraryName);
final boolean exists = nativeLib.exists();
debug("%s nativeLib %s exists = %s", SIMPLE_NAME, nativeLib, exists);
if (exists) {
return nativeLib;
}
}
// Load an OS-dependent native library inside a jar file
nativeLibraryPath = "/org/apache/commons/crypto/native/" + OsInfo.getNativeLibFolderPathForCurrentOS();
debug("%s nativeLibraryPath = %s", SIMPLE_NAME, nativeLibraryPath);
final String resource = nativeLibraryPath + File.separator + nativeLibraryName;
boolean hasNativeLib = hasResource(resource);
debug("%s resource %s exists = %s", SIMPLE_NAME, resource, hasNativeLib);
if (!hasNativeLib) {
final String altName = NATIVE_LIBNAME_ALT;
if (OsInfo.getOSName().equals("Mac") && hasResource(nativeLibraryPath + File.separator + altName)) {
// Fix for openjdk7 for Mac
nativeLibraryName = altName;
hasNativeLib = true;
}
}
if (!hasNativeLib) {
final String errorMessage = String.format("No native library is found for os.name=%s and os.arch=%s", OsInfo.getOSName(), OsInfo.getArchName());
throw new IllegalStateException(errorMessage);
}
// Temporary folder for the native lib. Use the value of
// Crypto.LIB_TEMPDIR_KEY or java.io.tmpdir
final String tempFolder = new File(props.getProperty(Crypto.LIB_TEMPDIR_KEY, System.getProperty("java.io.tmpdir"))).getAbsolutePath();
// Extract and load a native library inside the jar file
return extractLibraryFile(nativeLibraryPath, nativeLibraryName, tempFolder);
}
/**
* Gets the error cause if loading failed.
*
* @return null, unless loading failed
*/
static Throwable getLoadingError() {
return libraryLoadingError;
}
/**
* Checks whether the given path has resource.
*
* @param path the path.
* @return the boolean.
*/
private static boolean hasResource(final String path) {
return NativeCodeLoader.class.getResource(path) != null;
}
private static boolean isDebug() {
return Boolean.getBoolean(Crypto.CONF_PREFIX + "debug");
}
/**
* Checks whether native code is loaded for this platform.
*
* @return {@code true} if native is loaded, else {@code false}.
*/
static boolean isNativeCodeLoaded() {
return libraryLoaded;
}
/**
* Loads the library if possible.
*
* @return null if successful, otherwise the Throwable that was caught
*/
static Throwable loadLibrary() {
try {
final File nativeLibFile = findNativeLibrary();
if (nativeLibFile != null) {
// Load extracted or specified native library.
final String absolutePath = nativeLibFile.getAbsolutePath();
debug("%s System.load('%s')", SIMPLE_NAME, absolutePath);
System.load(absolutePath);
} else {
// Load preinstalled library (in the path -Djava.library.path)
final String libname = NATIVE_LIBNAME;
debug("%s System.loadLibrary('%s')", SIMPLE_NAME, libname);
System.loadLibrary(libname);
}
return null; // OK
} catch (final Exception | UnsatisfiedLinkError t) {
return t;
}
}
/**
* The private constructor of {@link NativeCodeLoader}.
*/
private NativeCodeLoader() {
}
}