• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * Copyright 2014 The Netty Project
19  *
20  * The Netty Project licenses this file to you under the Apache License,
21  * version 2.0 (the "License"); you may not use this file except in compliance
22  * with the License. You may obtain a copy of the License at:
23  *
24  *   http://www.apache.org/licenses/LICENSE-2.0
25  *
26  * Unless required by applicable law or agreed to in writing, software
27  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
28  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
29  * License for the specific language governing permissions and limitations
30  * under the License.
31  */
32 package org.conscrypt;
33 
34 import java.io.ByteArrayOutputStream;
35 import java.io.Closeable;
36 import java.io.File;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 import java.lang.reflect.Method;
42 import java.net.URL;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.util.Arrays;
46 import java.util.Locale;
47 
48 /**
49  * Helper class to load JNI resources.
50  *
51  */
52 final class NativeLibraryLoader {
53     private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
54     private static final String OSNAME;
55     private static final File WORKDIR;
56 
57     static {
58         OSNAME = System.getProperty("os.name", "")
59                          .toLowerCase(Locale.US)
60                          .replaceAll("[^a-z0-9]+", "");
61 
62         String workdir = System.getProperty("org.conscrypt.native.workdir");
63         if (workdir != null) {
64             File f = new File(workdir);
f.mkdirs()65             f.mkdirs();
66 
67             try {
68                 f = f.getAbsoluteFile();
69             } catch (Exception ignored) {
70                 // Good to have an absolute path, but it's OK.
71             }
72 
73             WORKDIR = f;
74         } else {
75             WORKDIR = tmpdir();
76         }
77     }
78 
tmpdir()79     private static File tmpdir() {
80         File f;
81         try {
82             f = toDirectory(System.getProperty("org.conscrypt.tmpdir"));
83             if (f != null) {
84                 return f;
85             }
86 
87             f = toDirectory(System.getProperty("java.io.tmpdir"));
88             if (f != null) {
89                 return f;
90             }
91 
92             // This shouldn't happen, but just in case ..
93             if (isWindows()) {
94                 f = toDirectory(System.getenv("TEMP"));
95                 if (f != null) {
96                     return f;
97                 }
98 
99                 String userprofile = System.getenv("USERPROFILE");
100                 if (userprofile != null) {
101                     f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
102                     if (f != null) {
103                         return f;
104                     }
105 
106                     f = toDirectory(userprofile + "\\Local Settings\\Temp");
107                     if (f != null) {
108                         return f;
109                     }
110                 }
111             } else {
112                 f = toDirectory(System.getenv("TMPDIR"));
113                 if (f != null) {
114                     return f;
115                 }
116             }
117         } catch (Exception ignored) {
118             // Environment variable inaccessible
119         }
120 
121         // Last resort.
122         if (isWindows()) {
123             f = new File("C:\\Windows\\Temp");
124         } else {
125             f = new File("/tmp");
126         }
127 
128         return f;
129     }
130 
131     @SuppressWarnings("ResultOfMethodCallIgnored")
toDirectory(String path)132     private static File toDirectory(String path) {
133         if (path == null) {
134             return null;
135         }
136 
137         File f = new File(path);
138         f.mkdirs();
139 
140         if (!f.isDirectory()) {
141             return null;
142         }
143 
144         try {
145             return f.getAbsoluteFile();
146         } catch (Exception ignored) {
147             return f;
148         }
149     }
150 
isWindows()151     private static boolean isWindows() {
152         return OSNAME.startsWith("windows");
153     }
154 
isOSX()155     private static boolean isOSX() {
156         return OSNAME.startsWith("macosx") || OSNAME.startsWith("osx");
157     }
158 
159     /**
160      * Loads the first available library in the collection with the specified
161      * {@link ClassLoader}.
162      *
163      * @throws IllegalArgumentException
164      *         if none of the given libraries load successfully.
165      */
loadFirstAvailable(ClassLoader loader, String... names)166     static void loadFirstAvailable(ClassLoader loader, String... names) {
167         for (String name : names) {
168             try {
169                 load(name, loader);
170                 return;
171             } catch (Throwable t) {
172                 // Do nothing.
173             }
174         }
175         throw new IllegalArgumentException(
176                 "Failed to load any of the given libraries: " + Arrays.toString(names));
177     }
178 
179     /**
180      * Load the given library with the specified {@link ClassLoader}
181      */
load(String name, ClassLoader loader)182     private static void load(String name, ClassLoader loader) {
183         String libname = System.mapLibraryName(name);
184         String path = NATIVE_RESOURCE_HOME + libname;
185 
186         URL url = loader.getResource(path);
187         if (url == null && isOSX()) {
188             if (path.endsWith(".jnilib")) {
189                 url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib");
190             } else {
191                 url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib");
192             }
193         }
194 
195         if (url == null) {
196             // Fall back to normal loading of JNI stuff
197             loadLibrary(loader, name, false);
198             return;
199         }
200 
201         int index = libname.lastIndexOf('.');
202         String prefix = libname.substring(0, index);
203         String suffix = libname.substring(index, libname.length());
204         InputStream in = null;
205         OutputStream out = null;
206         File tmpFile = null;
207         try {
208             tmpFile = createTempFile(prefix, suffix, WORKDIR);
209             in = url.openStream();
210             out = new FileOutputStream(tmpFile);
211 
212             byte[] buffer = new byte[8192];
213             int length;
214             while ((length = in.read(buffer)) > 0) {
215                 out.write(buffer, 0, length);
216             }
217             out.flush();
218 
219             // Close the output stream before loading the unpacked library,
220             // because otherwise Windows will refuse to load it when it's in use by other process.
221             closeQuietly(out);
222             out = null;
223 
224             loadLibrary(loader, tmpFile.getPath(), true);
225         } catch (Exception e) {
226             throw(UnsatisfiedLinkError) new UnsatisfiedLinkError(
227                     "could not load a native library: " + name)
228                     .initCause(e);
229         } finally {
230             closeQuietly(in);
231             closeQuietly(out);
232             // After we load the library it is safe to delete the file.
233             // We delete the file immediately to free up resources as soon as possible,
234             // and if this fails fallback to deleting on JVM exit.
235             if (tmpFile != null && !tmpFile.delete()) {
236                 tmpFile.deleteOnExit();
237             }
238         }
239     }
240 
241     /**
242      * Loading the native library into the specified {@link ClassLoader}.
243      * @param loader - The {@link ClassLoader} where the native library will be loaded into
244      * @param name - The native library path or name
245      * @param absolute - Whether the native library will be loaded by path or by name
246      */
loadLibrary( final ClassLoader loader, final String name, final boolean absolute)247     private static void loadLibrary(
248             final ClassLoader loader, final String name, final boolean absolute) {
249         try {
250             // Make sure the helper is belong to the target ClassLoader.
251             final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
252             loadLibraryByHelper(newHelper, name, absolute);
253             return;
254         } catch (UnsatisfiedLinkError e) { // Should by pass the UnsatisfiedLinkError here!
255             // Do nothing.
256         } catch (Exception e) {
257             // Do nothing.
258         }
259         NativeLibraryUtil.loadLibrary(name, absolute); // Fallback to local helper class.
260     }
261 
loadLibraryByHelper(final Class<?> helper, final String name, final boolean absolute)262     private static void loadLibraryByHelper(final Class<?> helper, final String name,
263             final boolean absolute) throws UnsatisfiedLinkError {
264         Object ret = AccessController.doPrivileged(new PrivilegedAction<Object>() {
265             @Override
266             public Object run() {
267                 try {
268                     // Invoke the helper to load the native library, if succeed, then the native
269                     // library belong to the specified ClassLoader.
270                     Method method = helper.getMethod("loadLibrary", String.class, boolean.class);
271                     method.setAccessible(true);
272                     return method.invoke(null, name, absolute);
273                 } catch (Exception e) {
274                     return e;
275                 }
276             }
277         });
278         if (ret instanceof Throwable) {
279             Throwable error = (Throwable) ret;
280             Throwable cause = error.getCause();
281             if (cause != null) {
282                 if (cause instanceof UnsatisfiedLinkError) {
283                     throw(UnsatisfiedLinkError) cause;
284                 } else {
285                     throw new UnsatisfiedLinkError(cause.getMessage());
286                 }
287             }
288             throw new UnsatisfiedLinkError(error.getMessage());
289         }
290     }
291 
292     /**
293      * Try to load the helper {@link Class} into specified {@link ClassLoader}.
294      * @param loader - The {@link ClassLoader} where to load the helper {@link Class}
295      * @param helper - The helper {@link Class}
296      * @return A new helper Class defined in the specified ClassLoader.
297      * @throws ClassNotFoundException Helper class not found or loading failed
298      */
tryToLoadClass(final ClassLoader loader, final Class<?> helper)299     private static Class<?> tryToLoadClass(final ClassLoader loader, final Class<?> helper)
300             throws ClassNotFoundException {
301         try {
302             return loader.loadClass(helper.getName());
303         } catch (ClassNotFoundException e) {
304             // The helper class is NOT found in target ClassLoader, we have to define the helper
305             // class.
306             final byte[] classBinary = classToByteArray(helper);
307             return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
308                 @Override
309                 public Class<?> run() {
310                     try {
311                         // Define the helper class in the target ClassLoader,
312                         //  then we can call the helper to load the native library.
313                         Method defineClass = ClassLoader.class.getDeclaredMethod(
314                                 "defineClass", String.class, byte[].class, int.class, int.class);
315                         defineClass.setAccessible(true);
316                         return (Class<?>) defineClass.invoke(
317                                 loader, helper.getName(), classBinary, 0, classBinary.length);
318                     } catch (Exception e) {
319                         throw new IllegalStateException("Define class failed!", e);
320                     }
321                 }
322             });
323         }
324     }
325 
326     /**
327      * Load the helper {@link Class} as a byte array, to be redefined in specified {@link
328      * ClassLoader}.
329      * @param clazz - The helper {@link Class} provided by this bundle
330      * @return The binary content of helper {@link Class}.
331      * @throws ClassNotFoundException Helper class not found or loading failed
332      */
classToByteArray(Class<?> clazz)333     private static byte[] classToByteArray(Class<?> clazz) throws ClassNotFoundException {
334         String fileName = clazz.getName();
335         int lastDot = fileName.lastIndexOf('.');
336         if (lastDot > 0) {
337             fileName = fileName.substring(lastDot + 1);
338         }
339         URL classUrl = clazz.getResource(fileName + ".class");
340         if (classUrl == null) {
341             throw new ClassNotFoundException(clazz.getName());
342         }
343         byte[] buf = new byte[1024];
344         ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
345         InputStream in = null;
346         try {
347             in = classUrl.openStream();
348             for (int r; (r = in.read(buf)) != -1;) {
349                 out.write(buf, 0, r);
350             }
351             return out.toByteArray();
352         } catch (IOException ex) {
353             throw new ClassNotFoundException(clazz.getName(), ex);
354         } finally {
355             closeQuietly(in);
356             closeQuietly(out);
357         }
358     }
359 
closeQuietly(Closeable c)360     private static void closeQuietly(Closeable c) {
361         if (c != null) {
362             try {
363                 c.close();
364             } catch (IOException ignore) {
365                 // ignore
366             }
367         }
368     }
369 
370     // Approximates the behavior of File.createTempFile without depending on SecureRandom.
createTempFile(String prefix, String suffix, File directory)371     private static File createTempFile(String prefix, String suffix, File directory)
372             throws IOException {
373         if (directory == null) {
374             throw new NullPointerException();
375         }
376         long time = System.currentTimeMillis();
377         prefix = new File(prefix).getName();
378         IOException suppressed = null;
379         for (int i = 0; i < 10000; i++) {
380             String tempName = String.format("%s%d%04d%s", prefix, time, i, suffix);
381             File tempFile = new File(directory, tempName);
382             if (!tempName.equals(tempFile.getName())) {
383                 // The given prefix or suffix contains path separators.
384                 throw new IOException("Unable to create temporary file: " + tempFile);
385             }
386             try {
387                 if (tempFile.createNewFile()) {
388                     return tempFile.getCanonicalFile();
389                 }
390             } catch (IOException e) {
391                 // This may just be a transient error; store it just in case.
392                 suppressed = e;
393             }
394         }
395         if (suppressed != null) {
396             throw suppressed;
397         } else {
398             throw new IOException("Unable to create temporary file");
399         }
400     }
401 
NativeLibraryLoader()402     private NativeLibraryLoader() {
403         // Utility
404     }
405 }
406