NativeCodeLoader.java

  1.  /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *     http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing, software
  13.  * distributed under the License is distributed on an "AS IS" BASIS,
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  * See the License for the specific language governing permissions and
  16.  * limitations under the License.
  17.  */
  18. package org.apache.commons.crypto;

  19. import java.io.BufferedInputStream;
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.nio.file.Files;
  24. import java.nio.file.Path;
  25. import java.nio.file.StandardCopyOption;
  26. import java.nio.file.attribute.PosixFileAttributes;
  27. import java.util.Objects;
  28. import java.util.Properties;
  29. import java.util.UUID;

  30. import org.apache.commons.crypto.utils.Utils;

  31. /**
  32.  * A helper to load the native code i.e. libcommons-crypto.so. This handles the
  33.  * fallback to either the bundled libcommons-crypto-Linux-i386-32.so or the
  34.  * default java implementations where appropriate.
  35.  */
  36. final class NativeCodeLoader {

  37.     private static final String SIMPLE_NAME = NativeCodeLoader.class.getSimpleName();

  38.     private static final String NATIVE_LIBNAME = "commons-crypto";

  39.     private static final String NATIVE_LIBNAME_ALT = "lib" + NATIVE_LIBNAME + ".jnilib";

  40.     /**
  41.      * End of file pseudo-character.
  42.      */
  43.     private static final int EOF = -1;

  44.     private static final Throwable libraryLoadingError;

  45.     private static final boolean libraryLoaded;

  46.     static {
  47.         debug("%s static init start", SIMPLE_NAME);
  48.         libraryLoadingError = loadLibrary(); // will be null if loaded OK
  49.         libraryLoaded = libraryLoadingError == null;
  50.         debug("%s libraryLoaded = %s, libraryLoadingError = %s", SIMPLE_NAME, libraryLoaded, libraryLoadingError);
  51.         debug("%s static init end", SIMPLE_NAME);
  52.     }

  53.     /**
  54.      * Returns the given InputStream if it is already a {@link BufferedInputStream},
  55.      * otherwise creates a BufferedInputStream from the given InputStream.
  56.      * <p>
  57.      * Copied from Apache Commons IO 2.5.
  58.      * </p>
  59.      *
  60.      * @param inputStream the InputStream to wrap or return (not null)
  61.      * @return the given InputStream or a new {@link BufferedInputStream} for the
  62.      *         given InputStream
  63.      * @throws NullPointerException if the input parameter is null
  64.      */
  65.     @SuppressWarnings("resource")
  66.     private static BufferedInputStream buffer(final InputStream inputStream) {
  67.         // reject null early on rather than waiting for IO operation to fail
  68.         // not checked by BufferedInputStream
  69.         Objects.requireNonNull(inputStream, "inputStream");
  70.         return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream
  71.                 : new BufferedInputStream(inputStream);
  72.     }

  73.     /**
  74.      * Checks whether in1 and in2 is equal.
  75.      * <p>
  76.      * Copied from Apache Commons IO 2.5.
  77.      * </p>
  78.      *
  79.      * @param input1 the input1.
  80.      * @param input2 the input2.
  81.      * @return true if in1 and in2 is equal, else false.
  82.      * @throws IOException if an I/O error occurs.
  83.      */
  84.     @SuppressWarnings("resource")
  85.     private static boolean contentsEquals(final InputStream input1, final InputStream input2) throws IOException {
  86.         if (input1 == input2) {
  87.             return true;
  88.         }
  89.         if (input1 == null ^ input2 == null) {
  90.             return false;
  91.         }
  92.         final BufferedInputStream bufferedInput1 = buffer(input1);
  93.         final BufferedInputStream bufferedInput2 = buffer(input2);
  94.         int ch = bufferedInput1.read();
  95.         while (EOF != ch) {
  96.             final int ch2 = bufferedInput2.read();
  97.             if (ch != ch2) {
  98.                 return false;
  99.             }
  100.             ch = bufferedInput1.read();
  101.         }
  102.         return bufferedInput2.read() == EOF;
  103.     }

  104.     /**
  105.      * Logs debug messages.
  106.      *
  107.      * @param format See {@link String#format(String, Object...)}.
  108.      * @param args   See {@link String#format(String, Object...)}.
  109.      */
  110.     private static void debug(final String format, final Object... args) {
  111.         // TODO Find a better way to do this later.
  112.         if (isDebug()) {
  113.             System.out.println(String.format(format, args));
  114.             if (args != null && args.length > 0 && args[0] instanceof Throwable) {
  115.                 ((Throwable) args[0]).printStackTrace(System.out);
  116.             }
  117.         }
  118.     }

  119.     /**
  120.      * Extracts the specified library file to the target folder.
  121.      *
  122.      * @param libFolderForCurrentOS the library in commons-crypto.lib.path.
  123.      * @param libraryFileName       the library name.
  124.      * @param targetFolder          Target folder for the native lib. Use the value
  125.      *                              of commons-crypto.tempdir or java.io.tmpdir.
  126.      * @return the library file.
  127.      */
  128.     private static File extractLibraryFile(final String libFolderForCurrentOS, final String libraryFileName,
  129.             final String targetFolder) {
  130.         final String nativeLibraryFilePath = libFolderForCurrentOS + File.separator + libraryFileName;

  131.         // Attach UUID to the native library file to ensure multiple class loaders
  132.         // can read the libcommons-crypto multiple times.
  133.         final UUID uuid = UUID.randomUUID();
  134.         final String extractedLibFileName = String.format("commons-crypto-%s-%s", uuid, libraryFileName);
  135.         final File extractedLibFile = new File(targetFolder, extractedLibFileName);
  136.         debug("Extracting '%s' to '%s'...", nativeLibraryFilePath, extractedLibFile);
  137.         try (InputStream inputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
  138.             if (inputStream == null) {
  139.                 debug("Resource not found: %s", nativeLibraryFilePath);
  140.                 return null;
  141.             }
  142.             // Extract a native library file into the target directory
  143.             final Path path;
  144.             try {
  145.                 path = extractedLibFile.toPath();
  146.                 final long byteCount = Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
  147.                 if (isDebug()) {
  148.                     debug("Extracted '%s' to '%s': %,d bytes [%s]", nativeLibraryFilePath, extractedLibFile, byteCount,
  149.                             Files.isExecutable(path) ? "X+" : "X-");
  150.                     final PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class);
  151.                     if (attributes != null) {
  152.                         debug("Attributes '%s': %s %s %s", extractedLibFile, attributes.permissions(),
  153.                                 attributes.owner(), attributes.group());
  154.                     }
  155.                 }
  156.             } finally {
  157.                 // Delete the extracted lib file on JVM exit.
  158.                 debug("Delete on exit: %s", extractedLibFile);
  159.                 extractedLibFile.deleteOnExit();
  160.             }

  161.             // Set executable (x) flag to enable Java to load the native library
  162.             if (!extractedLibFile.setReadable(true) || !extractedLibFile.setExecutable(true)
  163.                     || !extractedLibFile.setWritable(true, true)) {
  164.                 throw new IllegalStateException("Invalid path for library path " + extractedLibFile);
  165.             }

  166.             // Check whether the contents are properly copied from the resource
  167.             // folder
  168.             try (InputStream nativeInputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
  169.                 try (InputStream extractedLibIn = Files.newInputStream(path)) {
  170.                     debug("Validating '%s'...", extractedLibFile);
  171.                     if (!contentsEquals(nativeInputStream, extractedLibIn)) {
  172.                         throw new IllegalStateException(String.format("Failed to write a native library file %s to %s",
  173.                                 nativeLibraryFilePath, extractedLibFile));
  174.                     }
  175.                 }
  176.             }
  177.             return extractedLibFile;
  178.         } catch (final IOException e) {
  179.             debug("Ignoring %s", e);
  180.             return null;
  181.         }
  182.     }

  183.     /**
  184.      * Finds the native library.
  185.      *
  186.      * @return the jar file.
  187.      */
  188.     private static File findNativeLibrary() {
  189.         // Get the properties once
  190.         final Properties props = Utils.getDefaultProperties();

  191.         // Try to load the library in commons-crypto.lib.path */
  192.         String nativeLibraryPath = props.getProperty(Crypto.LIB_PATH_KEY);
  193.         String nativeLibraryName = props.getProperty(Crypto.LIB_NAME_KEY, System.mapLibraryName(NATIVE_LIBNAME));

  194.         debug("%s nativeLibraryPath %s = %s", SIMPLE_NAME, Crypto.LIB_PATH_KEY, nativeLibraryPath);
  195.         debug("%s nativeLibraryName %s = %s", SIMPLE_NAME, Crypto.LIB_NAME_KEY, nativeLibraryName);

  196.         if (nativeLibraryPath != null) {
  197.             final File nativeLib = new File(nativeLibraryPath, nativeLibraryName);
  198.             final boolean exists = nativeLib.exists();
  199.             debug("%s nativeLib %s exists = %s", SIMPLE_NAME, nativeLib, exists);
  200.             if (exists) {
  201.                 return nativeLib;
  202.             }
  203.         }

  204.         // Load an OS-dependent native library inside a jar file
  205.         nativeLibraryPath = "/org/apache/commons/crypto/native/" + OsInfo.getNativeLibFolderPathForCurrentOS();
  206.         debug("%s nativeLibraryPath = %s", SIMPLE_NAME, nativeLibraryPath);
  207.         final String resource = nativeLibraryPath + File.separator + nativeLibraryName;
  208.         boolean hasNativeLib = hasResource(resource);
  209.         debug("%s resource %s exists = %s", SIMPLE_NAME, resource, hasNativeLib);
  210.         if (!hasNativeLib) {
  211.             final String altName = NATIVE_LIBNAME_ALT;
  212.             if (OsInfo.getOSName().equals("Mac") && hasResource(nativeLibraryPath + File.separator + altName)) {
  213.                 // Fix for openjdk7 for Mac
  214.                 nativeLibraryName = altName;
  215.                 hasNativeLib = true;
  216.             }
  217.         }

  218.         if (!hasNativeLib) {
  219.             final String errorMessage = String.format("No native library is found for os.name=%s and os.arch=%s", OsInfo.getOSName(), OsInfo.getArchName());
  220.             throw new IllegalStateException(errorMessage);
  221.         }

  222.         // Temporary folder for the native lib. Use the value of
  223.         // Crypto.LIB_TEMPDIR_KEY or java.io.tmpdir
  224.         final String tempFolder = new File(props.getProperty(Crypto.LIB_TEMPDIR_KEY, System.getProperty("java.io.tmpdir"))).getAbsolutePath();

  225.         // Extract and load a native library inside the jar file
  226.         return extractLibraryFile(nativeLibraryPath, nativeLibraryName, tempFolder);
  227.     }

  228.     /**
  229.      * Gets the error cause if loading failed.
  230.      *
  231.      * @return null, unless loading failed
  232.      */
  233.     static Throwable getLoadingError() {
  234.         return libraryLoadingError;
  235.     }

  236.     /**
  237.      * Checks whether the given path has resource.
  238.      *
  239.      * @param path the path.
  240.      * @return the boolean.
  241.      */
  242.     private static boolean hasResource(final String path) {
  243.         return NativeCodeLoader.class.getResource(path) != null;
  244.     }

  245.     private static boolean isDebug() {
  246.         return Boolean.getBoolean(Crypto.CONF_PREFIX + "debug");
  247.     }

  248.     /**
  249.      * Checks whether native code is loaded for this platform.
  250.      *
  251.      * @return {@code true} if native is loaded, else {@code false}.
  252.      */
  253.     static boolean isNativeCodeLoaded() {
  254.         return libraryLoaded;
  255.     }

  256.     /**
  257.      * Loads the library if possible.
  258.      *
  259.      * @return null if successful, otherwise the Throwable that was caught
  260.      */
  261.     static Throwable loadLibrary() {
  262.         try {
  263.             final File nativeLibFile = findNativeLibrary();
  264.             if (nativeLibFile != null) {
  265.                 // Load extracted or specified native library.
  266.                 final String absolutePath = nativeLibFile.getAbsolutePath();
  267.                 debug("%s System.load('%s')", SIMPLE_NAME, absolutePath);
  268.                 System.load(absolutePath);
  269.             } else {
  270.                 // Load preinstalled library (in the path -Djava.library.path)
  271.                 final String libname = NATIVE_LIBNAME;
  272.                 debug("%s System.loadLibrary('%s')", SIMPLE_NAME, libname);
  273.                 System.loadLibrary(libname);
  274.             }
  275.             return null; // OK
  276.         } catch (final Exception | UnsatisfiedLinkError t) {
  277.             return t;
  278.         }
  279.     }

  280.     /**
  281.      * The private constructor of {@link NativeCodeLoader}.
  282.      */
  283.     private NativeCodeLoader() {
  284.     }
  285. }