1 /* 2 * Copyright (c) 2010, 2018, 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 }