1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.util.internal;
17
18 import io.netty.util.internal.logging.InternalLogger;
19 import io.netty.util.internal.logging.InternalLoggerFactory;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.Closeable;
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.lang.reflect.Method;
30 import java.net.URL;
31 import java.security.AccessController;
32 import java.security.PrivilegedAction;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.EnumSet;
36 import java.util.List;
37 import java.util.Set;
38
39
40
41
42
43 public final class NativeLibraryLoader {
44
45 private static final InternalLogger logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class);
46
47 private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
48 private static final File WORKDIR;
49 private static final boolean DELETE_NATIVE_LIB_AFTER_LOADING;
50
51 static {
52 String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
53 if (workdir != null) {
54 File f = new File(workdir);
55 f.mkdirs();
56
57 try {
58 f = f.getAbsoluteFile();
59 } catch (Exception ignored) {
60
61 }
62
63 WORKDIR = f;
64 logger.debug("-Dio.netty.native.workdir: " + WORKDIR);
65 } else {
66 WORKDIR = PlatformDependent.tmpdir();
67 logger.debug("-Dio.netty.native.workdir: " + WORKDIR + " (io.netty.tmpdir)");
68 }
69
70 DELETE_NATIVE_LIB_AFTER_LOADING = SystemPropertyUtil.getBoolean(
71 "io.netty.native.deleteLibAfterLoading", true);
72 }
73
74
75
76
77
78
79
80
81 public static void loadFirstAvailable(ClassLoader loader, String... names) {
82 List<Throwable> suppressed = new ArrayList<Throwable>();
83 for (String name : names) {
84 try {
85 load(name, loader);
86 return;
87 } catch (Throwable t) {
88 suppressed.add(t);
89 logger.debug("Unable to load the library '{}', trying next name...", name, t);
90 }
91 }
92 IllegalArgumentException iae =
93 new IllegalArgumentException("Failed to load any of the given libraries: " + Arrays.toString(names));
94 ThrowableUtil.addSuppressedAndClear(iae, suppressed);
95 throw iae;
96 }
97
98
99
100
101
102
103 private static String calculatePackagePrefix() {
104 String maybeShaded = NativeLibraryLoader.class.getName();
105
106 String expected = "io!netty!util!internal!NativeLibraryLoader".replace('!', '.');
107 if (!maybeShaded.endsWith(expected)) {
108 throw new UnsatisfiedLinkError(String.format(
109 "Could not find prefix added to %s to get %s. When shading, only adding a "
110 + "package prefix is supported", expected, maybeShaded));
111 }
112 return maybeShaded.substring(0, maybeShaded.length() - expected.length());
113 }
114
115
116
117
118 public static void load(String originalName, ClassLoader loader) {
119
120 String name = calculatePackagePrefix().replace('.', '_') + originalName;
121 List<Throwable> suppressed = new ArrayList<Throwable>();
122 try {
123
124 loadLibrary(loader, name, false);
125 return;
126 } catch (Throwable ex) {
127 suppressed.add(ex);
128 logger.debug(
129 "{} cannot be loaded from java.libary.path, "
130 + "now trying export to -Dio.netty.native.workdir: {}", name, WORKDIR, ex);
131 }
132
133 String libname = System.mapLibraryName(name);
134 String path = NATIVE_RESOURCE_HOME + libname;
135
136 InputStream in = null;
137 OutputStream out = null;
138 File tmpFile = null;
139 URL url;
140 if (loader == null) {
141 url = ClassLoader.getSystemResource(path);
142 } else {
143 url = loader.getResource(path);
144 }
145 try {
146 if (url == null) {
147 if (PlatformDependent.isOsx()) {
148 String fileName = path.endsWith(".jnilib") ? NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib" :
149 NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib";
150 if (loader == null) {
151 url = ClassLoader.getSystemResource(fileName);
152 } else {
153 url = loader.getResource(fileName);
154 }
155 if (url == null) {
156 FileNotFoundException fnf = new FileNotFoundException(fileName);
157 ThrowableUtil.addSuppressedAndClear(fnf, suppressed);
158 throw fnf;
159 }
160 } else {
161 FileNotFoundException fnf = new FileNotFoundException(path);
162 ThrowableUtil.addSuppressedAndClear(fnf, suppressed);
163 throw fnf;
164 }
165 }
166
167 int index = libname.lastIndexOf('.');
168 String prefix = libname.substring(0, index);
169 String suffix = libname.substring(index, libname.length());
170
171 tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
172 in = url.openStream();
173 out = new FileOutputStream(tmpFile);
174
175 byte[] buffer = new byte[8192];
176 int length;
177 while ((length = in.read(buffer)) > 0) {
178 out.write(buffer, 0, length);
179 }
180 out.flush();
181
182
183
184 closeQuietly(out);
185 out = null;
186
187 loadLibrary(loader, tmpFile.getPath(), true);
188 } catch (UnsatisfiedLinkError e) {
189 try {
190 if (tmpFile != null && tmpFile.isFile() && tmpFile.canRead() &&
191 !NoexecVolumeDetector.canExecuteExecutable(tmpFile)) {
192 logger.info("{} exists but cannot be executed even when execute permissions set; " +
193 "check volume for \"noexec\" flag; use -Dio.netty.native.workdir=[path] " +
194 "to set native working directory separately.",
195 tmpFile.getPath());
196 }
197 } catch (Throwable t) {
198 suppressed.add(t);
199 logger.debug("Error checking if {} is on a file store mounted with noexec", tmpFile, t);
200 }
201
202 ThrowableUtil.addSuppressedAndClear(e, suppressed);
203 throw e;
204 } catch (Exception e) {
205 UnsatisfiedLinkError ule = new UnsatisfiedLinkError("could not load a native library: " + name);
206 ule.initCause(e);
207 ThrowableUtil.addSuppressedAndClear(ule, suppressed);
208 throw ule;
209 } finally {
210 closeQuietly(in);
211 closeQuietly(out);
212
213
214
215 if (tmpFile != null && (!DELETE_NATIVE_LIB_AFTER_LOADING || !tmpFile.delete())) {
216 tmpFile.deleteOnExit();
217 }
218 }
219 }
220
221
222
223
224
225
226
227 private static void loadLibrary(final ClassLoader loader, final String name, final boolean absolute) {
228 Throwable suppressed = null;
229 try {
230 try {
231
232 final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
233 loadLibraryByHelper(newHelper, name, absolute);
234 logger.debug("Successfully loaded the library {}", name);
235 return;
236 } catch (UnsatisfiedLinkError e) {
237 suppressed = e;
238 logger.debug("Unable to load the library '{}', trying other loading mechanism.", name, e);
239 } catch (Exception e) {
240 suppressed = e;
241 logger.debug("Unable to load the library '{}', trying other loading mechanism.", name, e);
242 }
243 NativeLibraryUtil.loadLibrary(name, absolute);
244 logger.debug("Successfully loaded the library {}", name);
245 } catch (UnsatisfiedLinkError ule) {
246 if (suppressed != null) {
247 ThrowableUtil.addSuppressed(ule, suppressed);
248 }
249 throw ule;
250 }
251 }
252
253 private static void loadLibraryByHelper(final Class<?> helper, final String name, final boolean absolute)
254 throws UnsatisfiedLinkError {
255 Object ret = AccessController.doPrivileged(new PrivilegedAction<Object>() {
256 @Override
257 public Object run() {
258 try {
259
260
261 Method method = helper.getMethod("loadLibrary", String.class, boolean.class);
262 method.setAccessible(true);
263 return method.invoke(null, name, absolute);
264 } catch (Exception e) {
265 return e;
266 }
267 }
268 });
269 if (ret instanceof Throwable) {
270 Throwable t = (Throwable) ret;
271 assert !(t instanceof UnsatisfiedLinkError) : t + " should be a wrapper throwable";
272 Throwable cause = t.getCause();
273 if (cause instanceof UnsatisfiedLinkError) {
274 throw (UnsatisfiedLinkError) cause;
275 }
276 UnsatisfiedLinkError ule = new UnsatisfiedLinkError(t.getMessage());
277 ule.initCause(t);
278 throw ule;
279 }
280 }
281
282
283
284
285
286
287
288
289 private static Class<?> tryToLoadClass(final ClassLoader loader, final Class<?> helper)
290 throws ClassNotFoundException {
291 try {
292 return Class.forName(helper.getName(), false, loader);
293 } catch (ClassNotFoundException e1) {
294 if (loader == null) {
295
296 throw e1;
297 }
298 try {
299
300 final byte[] classBinary = classToByteArray(helper);
301 return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
302 @Override
303 public Class<?> run() {
304 try {
305
306
307 Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,
308 byte[].class, int.class, int.class);
309 defineClass.setAccessible(true);
310 return (Class<?>) defineClass.invoke(loader, helper.getName(), classBinary, 0,
311 classBinary.length);
312 } catch (Exception e) {
313 throw new IllegalStateException("Define class failed!", e);
314 }
315 }
316 });
317 } catch (ClassNotFoundException e2) {
318 ThrowableUtil.addSuppressed(e2, e1);
319 throw e2;
320 } catch (RuntimeException e2) {
321 ThrowableUtil.addSuppressed(e2, e1);
322 throw e2;
323 } catch (Error e2) {
324 ThrowableUtil.addSuppressed(e2, e1);
325 throw e2;
326 }
327 }
328 }
329
330
331
332
333
334
335
336 private static byte[] classToByteArray(Class<?> clazz) throws ClassNotFoundException {
337 String fileName = clazz.getName();
338 int lastDot = fileName.lastIndexOf('.');
339 if (lastDot > 0) {
340 fileName = fileName.substring(lastDot + 1);
341 }
342 URL classUrl = clazz.getResource(fileName + ".class");
343 if (classUrl == null) {
344 throw new ClassNotFoundException(clazz.getName());
345 }
346 byte[] buf = new byte[1024];
347 ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
348 InputStream in = null;
349 try {
350 in = classUrl.openStream();
351 for (int r; (r = in.read(buf)) != -1;) {
352 out.write(buf, 0, r);
353 }
354 return out.toByteArray();
355 } catch (IOException ex) {
356 throw new ClassNotFoundException(clazz.getName(), ex);
357 } finally {
358 closeQuietly(in);
359 closeQuietly(out);
360 }
361 }
362
363 private static void closeQuietly(Closeable c) {
364 if (c != null) {
365 try {
366 c.close();
367 } catch (IOException ignore) {
368
369 }
370 }
371 }
372
373 private NativeLibraryLoader() {
374
375 }
376
377 private static final class NoexecVolumeDetector {
378
379 private static boolean canExecuteExecutable(File file) throws IOException {
380 if (PlatformDependent.javaVersion() < 7) {
381
382
383 return true;
384 }
385
386
387 if (file.canExecute()) {
388 return true;
389 }
390
391
392
393
394
395
396
397 Set<java.nio.file.attribute.PosixFilePermission> existingFilePermissions =
398 java.nio.file.Files.getPosixFilePermissions(file.toPath());
399 Set<java.nio.file.attribute.PosixFilePermission> executePermissions =
400 EnumSet.of(java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE,
401 java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE,
402 java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE);
403 if (existingFilePermissions.containsAll(executePermissions)) {
404 return false;
405 }
406
407 Set<java.nio.file.attribute.PosixFilePermission> newPermissions = EnumSet.copyOf(existingFilePermissions);
408 newPermissions.addAll(executePermissions);
409 java.nio.file.Files.setPosixFilePermissions(file.toPath(), newPermissions);
410 return file.canExecute();
411 }
412
413 private NoexecVolumeDetector() {
414
415 }
416 }
417 }