/*
 * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.glass.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;

public class NativeLibLoader {

    private static final HashSet<String> loaded = new HashSet<String>();

    public static synchronized void loadLibrary(String libname) {
        if (!loaded.contains(libname)) {
            StackWalker walker = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () ->
            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));
            Class caller = walker.getCallerClass();
            loadLibraryInternal(libname, null, caller);
            loaded.add(libname);
        }
    }

    public static synchronized void loadLibrary(String libname, List<String> dependencies) {
        if (!loaded.contains(libname)) {
            StackWalker walker = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () ->
            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));
            Class caller = walker.getCallerClass();
            loadLibraryInternal(libname, dependencies, caller);
            loaded.add(libname);
        }
    }

    private static boolean verbose = false;

    private static boolean usingModules = false;
    private static File libDir = null;
    private static String libPrefix = "";
    private static String libSuffix = "";

    static {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            verbose = Boolean.getBoolean("javafx.verbose");
            return null;
        });
    }

    private static String[] initializePath(String propname) {
        String ldpath = System.getProperty(propname, "");
        String ps = File.pathSeparator;
        int ldlen = ldpath.length();
        int i, j, n;
        // Count the separators in the path
        i = ldpath.indexOf(ps);
        n = 0;
        while (i >= 0) {
            n++;
            i = ldpath.indexOf(ps, i + 1);
        }

        // allocate the array of paths - n :'s = n + 1 path elements
        String[] paths = new String[n + 1];

        // Fill the array with paths from the ldpath
        n = i = 0;
        j = ldpath.indexOf(ps);
        while (j >= 0) {
            if (j - i > 0) {
                paths[n++] = ldpath.substring(i, j);
            } else if (j - i == 0) {
                paths[n++] = ".";
            }
            i = j + 1;
            j = ldpath.indexOf(ps, i);
        }
        paths[n] = ldpath.substring(i, ldlen);
        return paths;
    }

    private static void loadLibraryInternal(String libraryName, List<String> dependencies, Class caller) {
        // The search order for native library loading is:
        // - try to load the native library from the same folder as this jar
        //   (only on non-modular builds)
        // - if the native library comes bundled as a resource it is extracted
        //   and loaded
        // - the java.library.path is searched for the library in definition
        //   order
        // - the library is loaded via System#loadLibrary
        // - on iOS native library is staticly linked and detected from the
        //   existence of a JNI_OnLoad_libraryname funtion
        try {
            // FIXME: JIGSAW -- We should eventually remove this legacy path,
            // since it isn't applicable to Jigsaw.
            loadLibraryFullPath(libraryName);
        } catch (UnsatisfiedLinkError ex) {
            if (verbose && !usingModules) {
                System.err.println("WARNING: " + ex);
            }

            // if the library is available in the jar, copy it to cache and load it from there
            if (loadLibraryFromResource(libraryName, dependencies, caller)) {
                return;
            }

            // NOTE: First attempt to load the libraries from the java.library.path.
            // This allows FX to find more recent versions of the shared libraries
            // from java.library.path instead of ones that might be part of the JRE
            //
            String [] libPath = initializePath("java.library.path");
            for (int i=0; i<libPath.length; i++) {
                try {
                    String path = libPath[i];
                    if (!path.endsWith(File.separator)) path += File.separator;
                    String fileName = System.mapLibraryName(libraryName);
                    File libFile = new File(path + fileName);
                    System.load(libFile.getAbsolutePath());
                    if (verbose) {
                        System.err.println("Loaded " + libFile.getAbsolutePath()
                                + " from java.library.path");
                    }
                    return;
                } catch (UnsatisfiedLinkError ex3) {
                    // Fail silently and try the next directory in java.library.path
                }
            }

            // Finally we will use System.loadLibrary.
            try {
                System.loadLibrary(libraryName);
                if (verbose) {
                    System.err.println("System.loadLibrary("
                            + libraryName + ") succeeded");
                }
            } catch (UnsatisfiedLinkError ex2) {
                //On iOS we link all libraries staticaly. Presence of library
                //is recognized by existence of JNI_OnLoad_libraryname() C function.
                //If libraryname contains hyphen, it needs to be translated
                //to underscore to form valid C function indentifier.
                if ("ios".equals(System.getProperty("os.name").toLowerCase(Locale.ROOT))
                        && libraryName.contains("-")) {
                    libraryName = libraryName.replace("-", "_");
                    try {
                        System.loadLibrary(libraryName);
                        return;
                    } catch (UnsatisfiedLinkError ex3) {
                        throw ex3;
                    }
                }
                // Rethrow exception
                throw ex2;
            }
        }
    }

   
If there is a library with the platform-correct name at the root of the resources in this jar, use that.
/** * If there is a library with the platform-correct name at the * root of the resources in this jar, use that. */
private static boolean loadLibraryFromResource(String libraryName, List<String> dependencies, Class caller) { return installLibraryFromResource(libraryName, dependencies, caller, true); }
If there is a library with the platform-correct name at the root of the resources in this jar, install it. If load is true, also load it.
/** * If there is a library with the platform-correct name at the * root of the resources in this jar, install it. If load is true, also load it. */
private static boolean installLibraryFromResource(String libraryName, List<String> dependencies, Class caller, boolean load) { try { // first preload dependencies if (dependencies != null) { for (String dep: dependencies) { boolean hasdep = installLibraryFromResource(dep, null, caller, false); } } String reallib = "/"+System.mapLibraryName(libraryName); InputStream is = caller.getResourceAsStream(reallib); if (is != null) { String fp = cacheLibrary(is, reallib, caller); if (load) { System.load(fp); if (verbose) { System.err.println("Loaded library " + reallib + " from resource"); } } else if (verbose) { System.err.println("Unpacked library " + reallib + " from resource"); } return true; } } catch (Throwable t) { // we should only be here if the resource exists in the module, but // for some reasons it can't be loaded. System.err.println("Loading library " + libraryName + " from resource failed: " + t); t.printStackTrace(); } return false; } private static String cacheLibrary(InputStream is, String name, Class caller) throws IOException { String jfxVersion = System.getProperty("javafx.version", "versionless"); String userCache = System.getProperty("javafx.cachedir", ""); if (userCache.isEmpty()) { userCache = System.getProperty("user.home") + "/.openjfx/cache/" + jfxVersion; } File cacheDir = new File(userCache); boolean cacheDirOk = true; if (cacheDir.exists()) { if (!cacheDir.isDirectory()) { System.err.println("Cache exists but is not a directory: "+cacheDir); cacheDirOk = false; } } else { if (!cacheDir.mkdirs()) { System.err.println("Can not create cache at "+cacheDir); cacheDirOk = false; } } if (!cacheDir.canRead()) { // on some systems, directories in user.home can be written but not read. cacheDirOk = false; } if (!cacheDirOk) { String username = System.getProperty("user.name", "anonymous"); String tmpCache = System.getProperty("java.io.tmpdir") + "/.openjfx_" + username + "/cache/" + jfxVersion; cacheDir = new File(tmpCache); if (cacheDir.exists()) { if (!cacheDir.isDirectory()) { throw new IOException("Cache exists but is not a directory: "+cacheDir); } } else { if (!cacheDir.mkdirs()) { throw new IOException("Can not create cache at "+cacheDir); } } } // we have a cache directory. Add the file here File f = new File(cacheDir, name); // if it exists, calculate checksum and keep if same as inputstream. boolean write = true; if (f.exists()) { byte[] isHash; byte[] fileHash; try { DigestInputStream dis = new DigestInputStream(is, MessageDigest.getInstance("MD5")); dis.getMessageDigest().reset(); byte[] buffer = new byte[4096]; while (dis.read(buffer) != -1) { /* empty loop body is intentional */ } isHash = dis.getMessageDigest().digest(); is.close(); is = caller.getResourceAsStream(name); // mark/reset not supported, we have to reread } catch (NoSuchAlgorithmException nsa) { isHash = new byte[1]; } fileHash = calculateCheckSum(f); if (!Arrays.equals(isHash, fileHash)) { Files.delete(f.toPath()); } else { // hashes are the same, we already have the file. write = false; } } if (write) { Path path = f.toPath(); Files.copy(is, path); } String fp = f.getAbsolutePath(); return fp; } static byte[] calculateCheckSum(File file) { try { // not looking for security, just a checksum. MD5 should be faster than SHA try (final InputStream stream = new FileInputStream(file); final DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("MD5")); ) { dis.getMessageDigest().reset(); byte[] buffer = new byte[4096]; while (dis.read(buffer) != -1) { /* empty loop body is intentional */ } return dis.getMessageDigest().digest(); } } catch (IllegalArgumentException | NoSuchAlgorithmException | IOException | SecurityException e) { // IOException also covers MalformedURLException // SecurityException means some untrusted applet // Fall through... } return new byte[0]; }
Load the native library from the same directory as the jar file containing this class.
/** * Load the native library from the same directory as the jar file * containing this class. */
private static void loadLibraryFullPath(String libraryName) { try { if (usingModules) { throw new UnsatisfiedLinkError("ignored"); } if (libDir == null) { // Get the URL for this class, if it is a jar URL, then get the // filename associated with it. String theClassFile = "NativeLibLoader.class"; Class theClass = NativeLibLoader.class; String classUrlString = theClass.getResource(theClassFile).toString(); if (classUrlString.startsWith("jrt:")) { // Suppress warning messages usingModules = true; throw new UnsatisfiedLinkError("ignored"); } if (!classUrlString.startsWith("jar:file:") || classUrlString.indexOf('!') == -1) { throw new UnsatisfiedLinkError("Invalid URL for class: " + classUrlString); } // Strip out the "jar:" and everything after and including the "!" String tmpStr = classUrlString.substring(4, classUrlString.lastIndexOf('!')); // Strip everything after the last "/" or "\" to get rid of the jar filename int lastIndexOfSlash = Math.max(tmpStr.lastIndexOf('/'), tmpStr.lastIndexOf('\\')); // Set the native directory based on the OS String osName = System.getProperty("os.name"); String relativeDir = null; if (osName.startsWith("Windows")) { relativeDir = "../bin"; } else if (osName.startsWith("Mac")) { relativeDir = "."; } else if (osName.startsWith("Linux")) { relativeDir = "."; } // Location of native libraries relative to jar file String libDirUrlString = tmpStr.substring(0, lastIndexOfSlash) + "/" + relativeDir; libDir = new File(new URI(libDirUrlString).getPath()); // Set the lib prefix and suffix based on the OS if (osName.startsWith("Windows")) { libPrefix = ""; libSuffix = ".dll"; } else if (osName.startsWith("Mac")) { libPrefix = "lib"; libSuffix = ".dylib"; } else if (osName.startsWith("Linux")) { libPrefix = "lib"; libSuffix = ".so"; } } File libFile = new File(libDir, libPrefix + libraryName + libSuffix); String libFileName = libFile.getCanonicalPath(); try { System.load(libFileName); if (verbose) { System.err.println("Loaded " + libFile.getAbsolutePath() + " from relative path"); } } catch(UnsatisfiedLinkError ex) { throw ex; } } catch (Exception e) { // Throw UnsatisfiedLinkError for best compatibility with System.loadLibrary() throw (UnsatisfiedLinkError) new UnsatisfiedLinkError().initCause(e); } } }