1 /*
   2  * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.glass.utils;
  26 
  27 import java.io.File;
  28 import java.io.FileInputStream;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.net.URI;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.security.AccessController;
  35 import java.security.DigestInputStream;
  36 import java.security.MessageDigest;
  37 import java.security.NoSuchAlgorithmException;
  38 import java.security.PrivilegedAction;
  39 import java.util.Arrays;
  40 import java.util.HashSet;
  41 import java.util.List;
  42 
  43 public class NativeLibLoader {
  44 
  45     private static final HashSet<String> loaded = new HashSet<String>();
  46 
  47     public static synchronized void loadLibrary(String libname) {
  48         if (!loaded.contains(libname)) {
  49             StackWalker walker = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () ->
  50             StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));
  51             Class caller = walker.getCallerClass();
  52             loadLibraryInternal(libname, null, caller);
  53             loaded.add(libname);
  54         }
  55     }
  56 
  57     public static synchronized void loadLibrary(String libname, List<String> dependencies) {
  58         if (!loaded.contains(libname)) {
  59             StackWalker walker = AccessController.doPrivileged((PrivilegedAction<StackWalker>) () ->
  60             StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));
  61             Class caller = walker.getCallerClass();
  62             loadLibraryInternal(libname, dependencies, caller);
  63             loaded.add(libname);
  64         }
  65     }
  66 
  67     private static boolean verbose = false;
  68 
  69     private static boolean usingModules = false;
  70     private static File libDir = null;
  71     private static String libPrefix = "";
  72     private static String libSuffix = "";
  73 
  74     static {
  75         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
  76             verbose = Boolean.getBoolean("javafx.verbose");
  77             return null;
  78         });
  79     }
  80 
  81     private static String[] initializePath(String propname) {
  82         String ldpath = System.getProperty(propname, "");
  83         String ps = File.pathSeparator;
  84         int ldlen = ldpath.length();
  85         int i, j, n;
  86         // Count the separators in the path
  87         i = ldpath.indexOf(ps);
  88         n = 0;
  89         while (i >= 0) {
  90             n++;
  91             i = ldpath.indexOf(ps, i + 1);
  92         }
  93 
  94         // allocate the array of paths - n :'s = n + 1 path elements
  95         String[] paths = new String[n + 1];
  96 
  97         // Fill the array with paths from the ldpath
  98         n = i = 0;
  99         j = ldpath.indexOf(ps);
 100         while (j >= 0) {
 101             if (j - i > 0) {
 102                 paths[n++] = ldpath.substring(i, j);
 103             } else if (j - i == 0) {
 104                 paths[n++] = ".";
 105             }
 106             i = j + 1;
 107             j = ldpath.indexOf(ps, i);
 108         }
 109         paths[n] = ldpath.substring(i, ldlen);
 110         return paths;
 111     }
 112 
 113     private static void loadLibraryInternal(String libraryName, List<String> dependencies, Class caller) {
 114         // Look for the library in the same directory as the jar file
 115         // containing this class.
 116         // If that fails, then try System.loadLibrary.
 117         try {
 118             // FIXME: JIGSAW -- We should eventually remove this legacy path,
 119             // since it isn't applicable to Jigsaw.
 120             loadLibraryFullPath(libraryName);
 121         } catch (UnsatisfiedLinkError ex) {
 122             if (verbose && !usingModules) {
 123                 System.err.println("WARNING: " + ex);
 124             }
 125 
 126             // NOTE: First attempt to load the libraries from the java.library.path.
 127             // This allows FX to find more recent versions of the shared libraries
 128             // from java.library.path instead of ones that might be part of the JRE
 129             //
 130             String [] libPath = initializePath("java.library.path");
 131             for (int i=0; i<libPath.length; i++) {
 132                 try {
 133                     String path = libPath[i];
 134                     if (!path.endsWith(File.separator)) path += File.separator;
 135                     String fileName = System.mapLibraryName(libraryName);
 136                     File libFile = new File(path + fileName);
 137                     System.load(libFile.getAbsolutePath());
 138                     if (verbose) {
 139                         System.err.println("Loaded " + libFile.getAbsolutePath()
 140                                 + " from java.library.path");
 141                     }
 142                     return;
 143                 } catch (UnsatisfiedLinkError ex3) {
 144                     // Fail silently and try the next directory in java.library.path
 145                 }
 146             }
 147 
 148             // Finally we will use System.loadLibrary.
 149             try {
 150                 System.loadLibrary(libraryName);
 151                 if (verbose) {
 152                     System.err.println("System.loadLibrary("
 153                             + libraryName + ") succeeded");
 154                 }
 155             } catch (UnsatisfiedLinkError ex2) {
 156                 // if the library is available in the jar, copy it to cache and load it from there
 157                 if (loadLibraryFromResource(libraryName, dependencies, caller)) {
 158                     return;
 159                 }
 160                 //On iOS we link all libraries staticaly. Presence of library
 161                 //is recognized by existence of JNI_OnLoad_libraryname() C function.
 162                 //If libraryname contains hyphen, it needs to be translated
 163                 //to underscore to form valid C function indentifier.
 164                 if ("iOS".equals(System.getProperty("os.name"))
 165                         && libraryName.contains("-")) {
 166                     libraryName = libraryName.replace("-", "_");
 167                     try {
 168                         System.loadLibrary(libraryName);
 169                         return;
 170                     } catch (UnsatisfiedLinkError ex3) {
 171                         throw ex3;
 172                     }
 173                 }
 174                 // Rethrow exception
 175                 throw ex2;
 176             }
 177         }
 178     }
 179 
 180    /**
 181     * If there is a library with the platform-correct name at the
 182     * root of the resources in this jar, use that.
 183     */
 184     private static boolean loadLibraryFromResource(String libraryName, List<String> dependencies, Class caller) {
 185         return installLibraryFromResource(libraryName, dependencies, caller, true);
 186     }
 187 
 188    /**
 189     * If there is a library with the platform-correct name at the
 190     * root of the resources in this jar, install it. If load is true, also load it.
 191     */
 192     private static boolean installLibraryFromResource(String libraryName, List<String> dependencies, Class caller, boolean load) {
 193         try {
 194             // first preload dependencies
 195             if (dependencies != null) {
 196                 for (String dep: dependencies) {
 197                     boolean hasdep = installLibraryFromResource(dep, null, caller, false);
 198                 }
 199             }
 200             String reallib = "/"+System.mapLibraryName(libraryName);
 201             InputStream is = caller.getResourceAsStream(reallib);
 202             if (is != null) {
 203                 String fp = cacheLibrary(is, reallib, caller);
 204                 if (load) {
 205                     System.load(fp);
 206                     if (verbose) {
 207                         System.err.println("Loaded library " + reallib + " from resource");
 208                     }
 209                 } else if (verbose) {
 210                     System.err.println("Unpacked library " + reallib + " from resource");
 211                 }
 212                 return true;
 213             }
 214         } catch (Throwable t) {
 215             // we should only be here if the resource exists in the module, but
 216             // for some reasons it can't be loaded.
 217             System.err.println("Loading library " + libraryName + " from resource failed: " + t);
 218             t.printStackTrace();
 219         }
 220         return false;
 221     }
 222 
 223     private static String cacheLibrary(InputStream is, String name, Class caller) throws IOException {
 224         String jfxVersion = System.getProperty("javafx.version", "versionless");
 225         String userCache = System.getProperty("user.home") + "/.openjfx/cache/" + jfxVersion;
 226         File cacheDir = new File(userCache);
 227         if (cacheDir.exists()) {
 228             if (!cacheDir.isDirectory()) {
 229                 throw new IOException ("Cache exists but is not a directory: "+cacheDir);
 230             }
 231         } else {
 232             if (!cacheDir.mkdirs()) {
 233                 throw new IOException ("Can not create cache at "+cacheDir);
 234             }
 235         }
 236         // we have a cache directory. Add the file here
 237         File f = new File(cacheDir, name);
 238         // if it exists, calculate checksum and keep if same as inputstream.
 239         boolean write = true;
 240         if (f.exists()) {
 241             byte[] isHash;
 242             byte[] fileHash;
 243             try {
 244                 DigestInputStream dis = new DigestInputStream(is, MessageDigest.getInstance("MD5"));
 245                 dis.getMessageDigest().reset();
 246                 byte[] buffer = new byte[4096];
 247                 while (dis.read(buffer) != -1) { /* empty loop body is intentional */ }
 248                 isHash = dis.getMessageDigest().digest();
 249                 is.close();
 250                 is = caller.getResourceAsStream(name); // mark/reset not supported, we have to reread
 251             }
 252             catch (NoSuchAlgorithmException nsa) {
 253                 isHash = new byte[1];
 254             }
 255             fileHash = calculateCheckSum(f);
 256             if (!Arrays.equals(isHash, fileHash)) {
 257                 Files.delete(f.toPath());
 258             } else {
 259                 // hashes are the same, we already have the file.
 260                 write = false;
 261             }
 262         }
 263         if (write) {
 264             Path path = f.toPath();
 265             Files.copy(is, path);
 266         }
 267 
 268         String fp = f.getAbsolutePath();
 269         return fp;
 270     }
 271 
 272     static byte[] calculateCheckSum(File file) {
 273         try {
 274                 // not looking for security, just a checksum. MD5 should be faster than SHA
 275                 try (final InputStream stream = new FileInputStream(file);
 276                     final DigestInputStream dis = new DigestInputStream(stream, MessageDigest.getInstance("MD5")); ) {
 277                     dis.getMessageDigest().reset();
 278                     byte[] buffer = new byte[4096];
 279                     while (dis.read(buffer) != -1) { /* empty loop body is intentional */ }
 280                     return dis.getMessageDigest().digest();
 281                 }
 282 
 283         } catch (IllegalArgumentException | NoSuchAlgorithmException | IOException | SecurityException e) {
 284             // IOException also covers MalformedURLException
 285             // SecurityException means some untrusted applet
 286 
 287             // Fall through...
 288         }
 289         return new byte[0];
 290     }
 291 
 292 
 293     /**
 294      * Load the native library from the same directory as the jar file
 295      * containing this class.
 296      */
 297     private static void loadLibraryFullPath(String libraryName) {
 298         try {
 299             if (usingModules) {
 300                 throw new UnsatisfiedLinkError("ignored");
 301             }
 302             if (libDir == null) {
 303                 // Get the URL for this class, if it is a jar URL, then get the
 304                 // filename associated with it.
 305                 String theClassFile = "NativeLibLoader.class";
 306                 Class theClass = NativeLibLoader.class;
 307                 String classUrlString = theClass.getResource(theClassFile).toString();
 308                 if (classUrlString.startsWith("jrt:")) {
 309                     // Suppress warning messages
 310                     usingModules = true;
 311                     throw new UnsatisfiedLinkError("ignored");
 312                 }
 313                 if (!classUrlString.startsWith("jar:file:") || classUrlString.indexOf('!') == -1) {
 314                     throw new UnsatisfiedLinkError("Invalid URL for class: " + classUrlString);
 315                 }
 316                 // Strip out the "jar:" and everything after and including the "!"
 317                 String tmpStr = classUrlString.substring(4, classUrlString.lastIndexOf('!'));
 318                 // Strip everything after the last "/" or "\" to get rid of the jar filename
 319                 int lastIndexOfSlash = Math.max(tmpStr.lastIndexOf('/'), tmpStr.lastIndexOf('\\'));
 320 
 321                 // Set the native directory based on the OS
 322                 String osName = System.getProperty("os.name");
 323                 String relativeDir = null;
 324                 if (osName.startsWith("Windows")) {
 325                     relativeDir = "../bin";
 326                 } else if (osName.startsWith("Mac")) {
 327                     relativeDir = ".";
 328                 } else if (osName.startsWith("Linux")) {
 329                     relativeDir = ".";
 330                 }
 331 
 332                 // Location of native libraries relative to jar file
 333                 String libDirUrlString = tmpStr.substring(0, lastIndexOfSlash)
 334                         + "/" + relativeDir;
 335                 libDir = new File(new URI(libDirUrlString).getPath());
 336 
 337                 // Set the lib prefix and suffix based on the OS
 338                 if (osName.startsWith("Windows")) {
 339                     libPrefix = "";
 340                     libSuffix = ".dll";
 341                 } else if (osName.startsWith("Mac")) {
 342                     libPrefix = "lib";
 343                     libSuffix = ".dylib";
 344                 } else if (osName.startsWith("Linux")) {
 345                     libPrefix = "lib";
 346                     libSuffix = ".so";
 347                 }
 348             }
 349 
 350             File libFile = new File(libDir, libPrefix + libraryName + libSuffix);
 351             String libFileName = libFile.getCanonicalPath();
 352             try {
 353                 System.load(libFileName);
 354                 if (verbose) {
 355                     System.err.println("Loaded " + libFile.getAbsolutePath()
 356                             + " from relative path");
 357                 }
 358             } catch(UnsatisfiedLinkError ex) {
 359                 throw ex;
 360             }
 361         } catch (Exception e) {
 362             // Throw UnsatisfiedLinkError for best compatibility with System.loadLibrary()
 363             throw (UnsatisfiedLinkError) new UnsatisfiedLinkError().initCause(e);
 364         }
 365     }
 366 
 367 }