More robust native library discovery in Mac OS X

Motivation:

Some JDK versions of Mac OS X generates a JNI dynamic library with '.jnilib' extension rather than with '.dynlib' extension.  However, System.mapLibraryName() always returns 'lib<name>.dynlib'. As a result, NativeLibraryLoader fails to load the native library whose extension is .jnilib.

Modification:

Try to find both '.jnilib' and '.dynlib' resources on OS X.

Result:

Dynamic libraries are loaded correctly in Mac OS X, and thus we can continue the OpenSslEngine work.
This commit is contained in:
Trustin Lee 2014-05-11 18:53:15 +09:00
parent e26bbfd4a7
commit 2bc0eac704

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.util.Locale;
/** /**
* Helper class to load JNI resources. * Helper class to load JNI resources.
@ -33,9 +34,13 @@ public final class NativeLibraryLoader {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class);
private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
private static final String OSNAME;
private static final File WORKDIR; private static final File WORKDIR;
static { static {
OSNAME = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
String workdir = SystemPropertyUtil.get("io.netty.native.workdir"); String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
if (workdir != null) { if (workdir != null) {
File f = new File(workdir); File f = new File(workdir);
@ -52,73 +57,171 @@ public final class NativeLibraryLoader {
} }
WORKDIR = f; WORKDIR = f;
logger.debug("-Dio.netty.netty.workdir: {}", WORKDIR); logger.debug("-Dio.netty.netty.workdir: " + WORKDIR);
} else { } else {
WORKDIR = PlatformDependent.tmpdir(); WORKDIR = tmpdir();
logger.debug("-Dio.netty.netty.workdir: {} (io.netty.tmpdir)", WORKDIR); logger.debug("-Dio.netty.netty.workdir: " + WORKDIR + " (io.netty.tmpdir)");
} }
} }
private static File tmpdir() {
File f;
try {
f = toDirectory(SystemPropertyUtil.get("io.netty.tmpdir"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f);
return f;
}
f = toDirectory(SystemPropertyUtil.get("java.io.tmpdir"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (java.io.tmpdir)");
return f;
}
// This shouldn't happen, but just in case ..
if (isWindows()) {
f = toDirectory(System.getenv("TEMP"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%TEMP%)");
return f;
}
String userprofile = System.getenv("USERPROFILE");
if (userprofile != null) {
f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\AppData\\Local\\Temp)");
return f;
}
f = toDirectory(userprofile + "\\Local Settings\\Temp");
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " (%USERPROFILE%\\Local Settings\\Temp)");
return f;
}
}
} else {
f = toDirectory(System.getenv("TMPDIR"));
if (f != null) {
logger.debug("-Dio.netty.tmpdir: " + f + " ($TMPDIR)");
return f;
}
}
} catch (Exception ignored) {
// Environment variable inaccessible
}
// Last resort.
if (isWindows()) {
f = new File("C:\\Windows\\Temp");
} else {
f = new File("/tmp");
}
logger.warn("Failed to get the temporary directory; falling back to: " + f);
return f;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static File toDirectory(String path) {
if (path == null) {
return null;
}
File f = new File(path);
if (!f.exists()) {
f.mkdirs();
}
if (!f.isDirectory()) {
return null;
}
try {
return f.getAbsoluteFile();
} catch (Exception ignored) {
return f;
}
}
private static boolean isWindows() {
return OSNAME.startsWith("windows");
}
private static boolean isOSX() {
return OSNAME.startsWith("macosx") || OSNAME.startsWith("osx");
}
/** /**
* Load the given library with the specified {@link java.lang.ClassLoader} * Load the given library with the specified {@link java.lang.ClassLoader}
*/ */
public static void load(String name, ClassLoader loader) { public static void load(String name, ClassLoader loader) {
String libname = System.mapLibraryName(name); String libname = System.mapLibraryName(name);
String path = "META-INF/native/" + libname; String path = NATIVE_RESOURCE_HOME + libname;
URL url = loader.getResource(path); URL url = loader.getResource(path);
if (url == null && isOSX()) {
if (path.endsWith(".jnilib")) {
url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib");
} else {
url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib");
}
}
if (url == null) { if (url == null) {
// Fall back to normal loading of JNI stuff // Fall back to normal loading of JNI stuff
System.loadLibrary(name); System.loadLibrary(name);
} else { return;
int index = libname.lastIndexOf('.'); }
String prefix = libname.substring(0, index);
String suffix = libname.substring(index, libname.length());
InputStream in = null;
OutputStream out = null;
File tmpFile = null;
boolean loaded = false;
try {
tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
in = url.openStream();
out = new FileOutputStream(tmpFile);
byte[] buffer = new byte[8192]; int index = libname.lastIndexOf('.');
int length; String prefix = libname.substring(0, index);
while ((length = in.read(buffer)) > 0) { String suffix = libname.substring(index, libname.length());
out.write(buffer, 0, length); InputStream in = null;
} OutputStream out = null;
out.flush(); File tmpFile = null;
out.close(); boolean loaded = false;
out = null; try {
tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
in = url.openStream();
out = new FileOutputStream(tmpFile);
System.load(tmpFile.getPath()); byte[] buffer = new byte[8192];
loaded = true; int length;
} catch (Exception e) { while ((length = in.read(buffer)) > 0) {
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError( out.write(buffer, 0, length);
"could not load a native library: " + name).initCause(e); }
} finally { out.flush();
if (in != null) { out.close();
try { out = null;
in.close();
} catch (IOException ignore) { System.load(tmpFile.getPath());
// ignore loaded = true;
} } catch (Exception e) {
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError(
"could not load a native library: " + name).initCause(e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignore) {
// ignore
} }
if (out != null) { }
try { if (out != null) {
out.close(); try {
} catch (IOException ignore) { out.close();
// ignore } catch (IOException ignore) {
} // ignore
} }
if (tmpFile != null) { }
if (loaded) { if (tmpFile != null) {
if (loaded) {
tmpFile.deleteOnExit();
} else {
if (!tmpFile.delete()) {
tmpFile.deleteOnExit(); tmpFile.deleteOnExit();
} else {
if (!tmpFile.delete()) {
tmpFile.deleteOnExit();
}
} }
} }
} }