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 35ab47a5d5
commit 10ab0db8f5

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Locale;
/**
* 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 String NATIVE_RESOURCE_HOME = "META-INF/native/";
private static final String OSNAME;
private static final File WORKDIR;
static {
OSNAME = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
if (workdir != null) {
File f = new File(workdir);
@ -52,73 +57,171 @@ public final class NativeLibraryLoader {
}
WORKDIR = f;
logger.debug("-Dio.netty.netty.workdir: {}", WORKDIR);
logger.debug("-Dio.netty.netty.workdir: " + WORKDIR);
} else {
WORKDIR = PlatformDependent.tmpdir();
logger.debug("-Dio.netty.netty.workdir: {} (io.netty.tmpdir)", WORKDIR);
WORKDIR = tmpdir();
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}
*/
public static void load(String name, ClassLoader loader) {
String libname = System.mapLibraryName(name);
String path = "META-INF/native/" + libname;
String path = NATIVE_RESOURCE_HOME + libname;
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) {
// Fall back to normal loading of JNI stuff
System.loadLibrary(name);
} else {
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);
return;
}
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
out.flush();
out.close();
out = null;
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);
System.load(tmpFile.getPath());
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
}
byte[] buffer = new byte[8192];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
out.flush();
out.close();
out = null;
System.load(tmpFile.getPath());
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 {
out.close();
} catch (IOException ignore) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException ignore) {
// ignore
}
if (tmpFile != null) {
if (loaded) {
}
if (tmpFile != null) {
if (loaded) {
tmpFile.deleteOnExit();
} else {
if (!tmpFile.delete()) {
tmpFile.deleteOnExit();
} else {
if (!tmpFile.delete()) {
tmpFile.deleteOnExit();
}
}
}
}