1 /*
   2  * Copyright (c) 2020, 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 jdk.internal.loader;
  26 
  27 import jdk.internal.ref.CleanerFactory;
  28 import jdk.internal.util.StaticProperty;
  29 
  30 import java.io.File;
  31 import java.io.IOException;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.util.ArrayDeque;
  35 import java.util.Deque;
  36 import java.util.HashSet;
  37 import java.util.Map;
  38 import java.util.Set;
  39 import java.util.concurrent.ConcurrentHashMap;
  40 
  41 /**
  42  * Native libraries are loaded via {@link System#loadLibrary(String)},
  43  * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and
  44  * {@link Runtime#load(String)}.  They are caller-sensitive.
  45  *
  46  * Each class loader has a NativeLibraries instance to register all of its
  47  * loaded native libraries.  System::loadLibrary (and other APIs) only
  48  * allows a native library to be loaded by one class loader, i.e. one
  49  * NativeLibraries instance.  Any attempt to load a native library that
  50  * has already been loaded by a class loader with another class loader
  51  * will fail.
  52  */
  53 public final class NativeLibraries {
  54 
  55     private final Map<String, NativeLibrary> libraries = new ConcurrentHashMap<>();
  56     private final ClassLoader loader;
  57     private final Class<?> caller;      // may be null.  If not null, this is used as
  58                                         // fromClass as a fast-path.  See loadLibrary(String name).
  59     private final boolean searchJavaLibraryPath;
  60 
  61     public NativeLibraries(ClassLoader loader) {
  62         this(loader, null, loader != null ? true : false);
  63     }
  64     public NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) {
  65         if (caller != null && caller.getClassLoader() != loader) {
  66             throw new IllegalArgumentException(caller.getName() + " must be defined by " + loader);
  67         }
  68         this.loader = loader;
  69         this.caller = caller;
  70         this.searchJavaLibraryPath = searchJavaLibraryPath;
  71     }
  72 
  73     /*
  74      * Look up the address of the given symbol name from the native libraries
  75      * loaded in this NativeLibraries instance.
  76      */
  77     public long lookup(String name) {
  78         if (libraries.isEmpty())
  79             return 0;
  80 
  81         // the native libraries map may be updated in another thread
  82         // when a native library is being loaded.  No symbol will be
  83         // searched from it yet.
  84         for (NativeLibrary lib : libraries.values()) {
  85             long entry = lib.findEntry(name);
  86             if (entry != 0) return entry;
  87         }
  88         return 0;
  89     }
  90 
  91     /*
  92      * Load a native library from the given file.  Returns null if file does not exist.
  93      *
  94      * @param fromClass the caller class calling System::loadLibrary
  95      * @param file the path of the native library
  96      * @throws UnsatisfiedLinkError if any error in loading the native library
  97      */
  98     public NativeLibrary loadLibrary(Class<?> fromClass, File file) {
  99         // Check to see if we're attempting to access a static library
 100         String name = findBuiltinLib(file.getName());
 101         boolean isBuiltin = (name != null);
 102         if (!isBuiltin) {
 103             name = AccessController.doPrivileged(new PrivilegedAction<>() {
 104                 public String run() {
 105                     try {
 106                         return file.exists() ? file.getCanonicalPath() : null;
 107                     } catch (IOException e) {
 108                         return null;
 109                     }
 110                 }
 111             });
 112             if (name == null) {
 113                 return null;
 114             }
 115         }
 116         return loadLibrary(fromClass, name, isBuiltin);
 117     }
 118 
 119     /**
 120      * Returns a NativeLibrary of the given name.
 121      *
 122      * @param fromClass the caller class calling System::loadLibrary
 123      * @param name      library name
 124      * @param isBuiltin built-in library
 125      * @throws UnsatisfiedLinkError if the native library has already been loaded
 126      *      and registered in another NativeLibraries
 127      */
 128     private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
 129         ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
 130         if (this.loader != loader) {
 131             throw new InternalError(fromClass.getName() + " not allowed to load library");
 132         }
 133 
 134         synchronized (loadedLibraryNames) {
 135             // find if this library has already been loaded and registered in this NativeLibraries
 136             NativeLibrary cached = libraries.get(name);
 137             if (cached != null) {
 138                 return cached;
 139             }
 140 
 141             // cannot be loaded by other class loaders
 142             if (loadedLibraryNames.contains(name)) {
 143                 throw new UnsatisfiedLinkError("Native Library " + name +
 144                         " already loaded in another classloader");
 145             }
 146 
 147             /*
 148              * When a library is being loaded, JNI_OnLoad function can cause
 149              * another loadLibrary invocation that should succeed.
 150              *
 151              * We use a static stack to hold the list of libraries we are
 152              * loading because this can happen only when called by the
 153              * same thread because this block is synchronous.
 154              *
 155              * If there is a pending load operation for the library, we
 156              * immediately return success; otherwise, we raise
 157              * UnsatisfiedLinkError.
 158              */
 159             for (NativeLibraryImpl lib : nativeLibraryContext) {
 160                 if (name.equals(lib.name())) {
 161                     if (loader == lib.fromClass.getClassLoader()) {
 162                         return lib;
 163                     } else {
 164                         throw new UnsatisfiedLinkError("Native Library " +
 165                                 name + " is being loaded in another classloader");
 166                     }
 167                 }
 168             }
 169 
 170             NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin);
 171             // load the native library
 172             nativeLibraryContext.push(lib);
 173             try {
 174                 if (!lib.open()) return null;
 175             } finally {
 176                 nativeLibraryContext.pop();
 177             }
 178             // register the loaded native library
 179             loadedLibraryNames.add(name);
 180             libraries.put(name, lib);
 181             return lib;
 182         }
 183     }
 184 
 185     /**
 186      * Loads a native library from the system library path and java library path.
 187      *
 188      * @param name library name
 189      *
 190      * @throws UnsatisfiedLinkError if the native library has already been loaded
 191      *      and registered in another NativeLibraries
 192      */
 193     public NativeLibrary loadLibrary(String name) {
 194         assert caller != null;
 195         return loadLibrary(caller, name);
 196     }
 197 
 198     /**
 199      * Loads a native library from the system library path and java library path.
 200      *
 201      * @param name library name
 202      * @param fromClass the caller class calling System::loadLibrary
 203      *
 204      * @throws UnsatisfiedLinkError if the native library has already been loaded
 205      *      and registered in another NativeLibraries
 206      */
 207     public NativeLibrary loadLibrary(Class<?> fromClass, String name) {
 208         NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name);
 209         if (lib == null && searchJavaLibraryPath) {
 210             lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name);
 211         }
 212         return lib;
 213     }
 214 
 215     private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {
 216         for (String path : paths) {
 217             File libfile = new File(path, System.mapLibraryName(name));
 218             NativeLibrary nl = loadLibrary(fromClass, libfile);
 219             if (nl != null) {
 220                 return nl;
 221             }
 222             libfile = ClassLoaderHelper.mapAlternativeName(libfile);
 223             if (libfile != null) {
 224                 nl = loadLibrary(fromClass, libfile);
 225                 if (nl != null) {
 226                     return nl;
 227                 }
 228             }
 229         }
 230         return null;
 231     }
 232 
 233     /**
 234      * NativeLibraryImpl denotes a loaded native library instance.
 235      * Each NativeLibraries contains a map of loaded native libraries in the
 236      * private field {@code libraries}.
 237      *
 238      * Every native library requires a particular version of JNI. This is
 239      * denoted by the private {@code jniVersion} field.  This field is set by
 240      * the VM when it loads the library, and used by the VM to pass the correct
 241      * version of JNI to the native methods.
 242      */
 243     static class NativeLibraryImpl implements NativeLibrary {
 244         // the class from which the library is loaded, also indicates
 245         // the loader this native library belongs.
 246         final Class<?> fromClass;
 247         // the canonicalized name of the native library.
 248         // or static library name
 249         final String name;
 250         // Indicates if the native library is linked into the VM
 251         final boolean isBuiltin;
 252 
 253         // opaque handle to native library, used in native code.
 254         long handle;
 255         // the version of JNI environment the native library requires.
 256         int jniVersion;
 257 
 258         NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) {
 259             this.fromClass = fromClass;
 260             this.name = name;
 261             this.isBuiltin = isBuiltin;
 262         }
 263 
 264         @Override
 265         public String name() {
 266             return name;
 267         }
 268 
 269         @Override
 270         public long findEntry(String name) {
 271             return findEntry0(this, name);
 272         }
 273 
 274         /*
 275          * Loads the native library and registers for cleanup when its
 276          * associated class loader is unloaded
 277          */
 278         boolean open() {
 279             if (handle != 0) {
 280                 throw new InternalError("Native library " + name + " has been loaded");
 281             }
 282 
 283             if (!load(this, name, isBuiltin)) return false;
 284 
 285             // register the class loader for cleanup when unloaded
 286             // builtin class loaders are never unloaded
 287             ClassLoader loader = fromClass != null ? fromClass.getClassLoader() : null;
 288             if (loader != null &&
 289                     loader != ClassLoaders.platformClassLoader() &&
 290                     loader != ClassLoaders.appClassLoader()) {
 291                 CleanerFactory.cleaner().register(loader, new Unloader(name, handle, isBuiltin));
 292             }
 293             return true;
 294         }
 295     }
 296 
 297     /*
 298      * The run() method will be invoked when this class loader becomes
 299      * phantom reachable to unload the native library.
 300      */
 301     static class Unloader implements Runnable {
 302         // This represents the context when a native library is unloaded
 303         // and getFromClass() will return null,
 304         static final NativeLibraryImpl UNLOADER =
 305                 new NativeLibraryImpl(null, "dummy", false);
 306 
 307         final String name;
 308         final long handle;
 309         final boolean isBuiltin;
 310 
 311         Unloader(String name, long handle, boolean isBuiltin) {
 312             if (handle == 0) {
 313                 throw new IllegalArgumentException(
 314                         "Invalid handle for native library " + name);
 315             }
 316 
 317             this.name = name;
 318             this.handle = handle;
 319             this.isBuiltin = isBuiltin;
 320         }
 321 
 322         @Override
 323         public void run() {
 324             synchronized (NativeLibraries.loadedLibraryNames) {
 325                 /* remove the native library name */
 326                 NativeLibraries.loadedLibraryNames.remove(name);
 327                 NativeLibraries.nativeLibraryContext.push(UNLOADER);
 328                 try {
 329                     unload(name, isBuiltin, handle);
 330                 } finally {
 331                     NativeLibraries.nativeLibraryContext.pop();
 332                 }
 333             }
 334         }
 335     }
 336 
 337     /*
 338      * Holds system and user library paths derived from the
 339      * {@code java.library.path} and {@code sun.boot.library.path} system
 340      * properties. The system properties are eagerly read at bootstrap, then
 341      * lazily parsed on first use to avoid initialization ordering issues.
 342      */
 343     static class LibraryPaths {
 344         // The paths searched for libraries
 345         static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath());
 346         static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath());
 347     }
 348 
 349     // All native libraries we've loaded.
 350     // This also serves as the lock to obtain nativeLibraries
 351     // and write to nativeLibraryContext.
 352     private static final Set<String> loadedLibraryNames = new HashSet<>();
 353 
 354     // native libraries being loaded
 355     private static Deque<NativeLibraryImpl> nativeLibraryContext = new ArrayDeque<>(8);
 356 
 357     // Invoked in the VM to determine the context class in JNI_OnLoad
 358     // and JNI_OnUnload
 359     private static Class<?> getFromClass() {
 360         if (nativeLibraryContext.isEmpty()) { // only default library
 361             return Object.class;
 362         }
 363         return nativeLibraryContext.peek().fromClass;
 364     }
 365 
 366     // JNI FindClass expects the caller class if invoked from JNI_OnLoad
 367     // and JNI_OnUnload is NativeLibrary class
 368     private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin);
 369     private static native void unload(String name, boolean isBuiltin, long handle);
 370     private static native String findBuiltinLib(String name);
 371     private static native long findEntry0(NativeLibraryImpl lib, String name);
 372 }