PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java

https://gitlab.com/taichu/netty
Java | 346 lines | 247 code | 35 blank | 64 comment | 44 complexity | 83405a240f990096aee6d2412e7d8ce9 MD5 | raw file
  1. /*
  2. * Copyright 2014 The Netty Project
  3. *
  4. * The Netty Project licenses this file to you under the Apache License,
  5. * version 2.0 (the "License"); you may not use this file except in compliance
  6. * with the License. You may obtain a copy of the License at:
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations
  14. * under the License.
  15. */
  16. package io.netty.util.internal;
  17. import io.netty.util.internal.logging.InternalLogger;
  18. import io.netty.util.internal.logging.InternalLoggerFactory;
  19. import java.io.Closeable;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStream;
  26. import java.lang.reflect.Method;
  27. import java.net.URL;
  28. import java.security.AccessController;
  29. import java.security.PrivilegedAction;
  30. import java.util.Arrays;
  31. import java.util.Locale;
  32. /**
  33. * Helper class to load JNI resources.
  34. *
  35. */
  36. public final class NativeLibraryLoader {
  37. private static final InternalLogger logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class);
  38. private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
  39. private static final String OSNAME;
  40. private static final File WORKDIR;
  41. static {
  42. OSNAME = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
  43. String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
  44. if (workdir != null) {
  45. File f = new File(workdir);
  46. f.mkdirs();
  47. try {
  48. f = f.getAbsoluteFile();
  49. } catch (Exception ignored) {
  50. // Good to have an absolute path, but it's OK.
  51. }
  52. WORKDIR = f;
  53. logger.debug("-Dio.netty.native.workdir: " + WORKDIR);
  54. } else {
  55. WORKDIR = tmpdir();
  56. logger.debug("-Dio.netty.native.workdir: " + WORKDIR + " (io.netty.tmpdir)");
  57. }
  58. }
  59. private static File tmpdir() {
  60. File f;
  61. try {
  62. f = toDirectory(SystemPropertyUtil.get("io.netty.tmpdir"));
  63. if (f != null) {
  64. logger.debug("-Dio.netty.tmpdir: " + f);
  65. return f;
  66. }
  67. f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir"));
  68. if (f != null) {
  69. logger.debug("-Dio.netty.tmpdir: " + f + " (java.io.tmpdir)");
  70. return f;
  71. }
  72. // This shouldn't happen, but just in case ..
  73. if (isWindows()) {
  74. f = toDirectory(System.getenv("TEMP"));
  75. if (f != null) {
  76. logger.debug("-Dio.netty.tmpdir: " + f + " (%TEMP%)");
  77. return f;
  78. }
  79. String userprofile = System.getenv("USERPROFILE");
  80. if (userprofile != null) {
  81. f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
  82. if (f != null) {
  83. logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\AppData\\Local\\Temp)");
  84. return f;
  85. }
  86. f = toDirectory(userprofile + "\\Local Settings\\Temp");
  87. if (f != null) {
  88. logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\Local Settings\\Temp)");
  89. return f;
  90. }
  91. }
  92. } else {
  93. f = toDirectory(System.getenv("TMPDIR"));
  94. if (f != null) {
  95. logger.debug("-Dio.netty.tmpdir: " + f + " ($TMPDIR)");
  96. return f;
  97. }
  98. }
  99. } catch (Exception ignored) {
  100. // Environment variable inaccessible
  101. }
  102. // Last resort.
  103. if (isWindows()) {
  104. f = new File("C:\\Windows\\Temp");
  105. } else {
  106. f = new File("/tmp");
  107. }
  108. logger.warn("Failed to get the temporary directory; falling back to: " + f);
  109. return f;
  110. }
  111. @SuppressWarnings("ResultOfMethodCallIgnored")
  112. private static File toDirectory(String path) {
  113. if (path == null) {
  114. return null;
  115. }
  116. File f = new File(path);
  117. f.mkdirs();
  118. if (!f.isDirectory()) {
  119. return null;
  120. }
  121. try {
  122. return f.getAbsoluteFile();
  123. } catch (Exception ignored) {
  124. return f;
  125. }
  126. }
  127. private static boolean isWindows() {
  128. return OSNAME.startsWith("windows");
  129. }
  130. private static boolean isOSX() {
  131. return OSNAME.startsWith("macosx") || OSNAME.startsWith("osx");
  132. }
  133. /**
  134. * Loads the first available library in the collection with the specified
  135. * {@link ClassLoader}.
  136. *
  137. * @throws IllegalArgumentException
  138. * if none of the given libraries load successfully.
  139. */
  140. public static void loadFirstAvailable(ClassLoader loader, String... names) {
  141. for (String name : names) {
  142. try {
  143. load(name, loader);
  144. return;
  145. } catch (Throwable t) {
  146. logger.debug("Unable to load the library: " + name + '.', t);
  147. }
  148. }
  149. throw new IllegalArgumentException("Failed to load any of the given libraries: "
  150. + Arrays.toString(names));
  151. }
  152. /**
  153. * Load the given library with the specified {@link ClassLoader}
  154. */
  155. public static void load(String name, ClassLoader loader) {
  156. String libname = System.mapLibraryName(name);
  157. String path = NATIVE_RESOURCE_HOME + libname;
  158. URL url = loader.getResource(path);
  159. if (url == null && isOSX()) {
  160. if (path.endsWith(".jnilib")) {
  161. url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib");
  162. } else {
  163. url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib");
  164. }
  165. }
  166. if (url == null) {
  167. // Fall back to normal loading of JNI stuff
  168. loadLibrary(loader, name, false);
  169. return;
  170. }
  171. int index = libname.lastIndexOf('.');
  172. String prefix = libname.substring(0, index);
  173. String suffix = libname.substring(index, libname.length());
  174. InputStream in = null;
  175. OutputStream out = null;
  176. File tmpFile = null;
  177. try {
  178. tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
  179. in = url.openStream();
  180. out = new FileOutputStream(tmpFile);
  181. byte[] buffer = new byte[8192];
  182. int length;
  183. while ((length = in.read(buffer)) > 0) {
  184. out.write(buffer, 0, length);
  185. }
  186. out.flush();
  187. loadLibrary(loader, tmpFile.getPath(), true);
  188. } catch (Exception e) {
  189. throw (UnsatisfiedLinkError) new UnsatisfiedLinkError(
  190. "could not load a native library: " + name).initCause(e);
  191. } finally {
  192. closeQuietly(in);
  193. closeQuietly(out);
  194. // After we load the library it is safe to delete the file.
  195. // We delete the file immediately to free up resources as soon as possible,
  196. // and if this fails fallback to deleting on JVM exit.
  197. if (tmpFile != null && !tmpFile.delete()) {
  198. tmpFile.deleteOnExit();
  199. }
  200. }
  201. }
  202. /**
  203. * Loading the native library into the specified {@link ClassLoader}.
  204. * @param loader - The {@link ClassLoader} where the native library will be loaded into
  205. * @param name - The native library path or name
  206. * @param absolute - Whether the native library will be loaded by path or by name
  207. */
  208. private static void loadLibrary(final ClassLoader loader, final String name, final boolean absolute) {
  209. try {
  210. // Make sure the helper is belong to the target ClassLoader.
  211. final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
  212. loadLibraryByHelper(newHelper, name, absolute);
  213. } catch (Exception e) { // Should by pass the UnsatisfiedLinkError here!
  214. logger.debug("Unable to load the library: " + name + '.', e);
  215. NativeLibraryUtil.loadLibrary(name, absolute); // Fallback to local helper class.
  216. }
  217. }
  218. private static void loadLibraryByHelper(final Class<?> helper, final String name, final boolean absolute) {
  219. AccessController.doPrivileged(new PrivilegedAction<Object>() {
  220. @Override
  221. public Object run() {
  222. try {
  223. // Invoke the helper to load the native library, if succeed, then the native
  224. // library belong to the specified ClassLoader.
  225. Method method = helper.getMethod("loadLibrary",
  226. new Class<?>[] { String.class, boolean.class });
  227. method.setAccessible(true);
  228. return method.invoke(null, name, absolute);
  229. } catch (Exception e) {
  230. throw new IllegalStateException("Load library failed!", e);
  231. }
  232. }
  233. });
  234. }
  235. /**
  236. * Try to load the helper {@link Class} into specified {@link ClassLoader}.
  237. * @param loader - The {@link ClassLoader} where to load the helper {@link Class}
  238. * @param helper - The helper {@link Class}
  239. * @return A new helper Class defined in the specified ClassLoader.
  240. * @throws ClassNotFoundException Helper class not found or loading failed
  241. */
  242. private static Class<?> tryToLoadClass(final ClassLoader loader, final Class<?> helper)
  243. throws ClassNotFoundException {
  244. try {
  245. return loader.loadClass(helper.getName());
  246. } catch (ClassNotFoundException e) {
  247. // The helper class is NOT found in target ClassLoader, we have to define the helper class.
  248. final byte[] classBinary = classToByteArray(helper);
  249. return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
  250. @Override
  251. public Class<?> run() {
  252. try {
  253. // Define the helper class in the target ClassLoader,
  254. // then we can call the helper to load the native library.
  255. Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,
  256. byte[].class, int.class, int.class);
  257. defineClass.setAccessible(true);
  258. return (Class<?>) defineClass.invoke(loader, helper.getName(), classBinary, 0,
  259. classBinary.length);
  260. } catch (Exception e) {
  261. throw new IllegalStateException("Define class failed!", e);
  262. }
  263. }
  264. });
  265. }
  266. }
  267. /**
  268. * Load the helper {@link Class} as a byte array, to be redefined in specified {@link ClassLoader}.
  269. * @param clazz - The helper {@link Class} provided by this bundle
  270. * @return The binary content of helper {@link Class}.
  271. * @throws ClassNotFoundException Helper class not found or loading failed
  272. */
  273. private static byte[] classToByteArray(Class<?> clazz) throws ClassNotFoundException {
  274. String fileName = clazz.getName();
  275. int lastDot = fileName.lastIndexOf('.');
  276. if (lastDot > 0) {
  277. fileName = fileName.substring(lastDot + 1);
  278. }
  279. URL classUrl = clazz.getResource(fileName + ".class");
  280. if (classUrl == null) {
  281. throw new ClassNotFoundException(clazz.getName());
  282. }
  283. byte[] buf = new byte[1024];
  284. ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
  285. InputStream in = null;
  286. try {
  287. in = classUrl.openStream();
  288. for (int r; (r = in.read(buf)) != -1;) {
  289. out.write(buf, 0, r);
  290. }
  291. return out.toByteArray();
  292. } catch (IOException ex) {
  293. throw new ClassNotFoundException(clazz.getName(), ex);
  294. } finally {
  295. closeQuietly(in);
  296. closeQuietly(out);
  297. }
  298. }
  299. private static void closeQuietly(Closeable c) {
  300. if (c != null) {
  301. try {
  302. c.close();
  303. } catch (IOException ignore) {
  304. // ignore
  305. }
  306. }
  307. }
  308. private NativeLibraryLoader() {
  309. // Utility
  310. }
  311. }