Skip to content

Add jffi.extract.name for specific file #99

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@

<target name="test" depends="compile,-compile-test">
<mkdir dir="${build.test.dir}/results"/>

<echo>test with jni libraries from sources</echo>
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
<env key="LC_ALL" value="C"/>
<classpath>
Expand All @@ -177,7 +179,45 @@
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>

<echo>test with unpack of jni library</echo>
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
<env key="LC_ALL" value="C"/>
<classpath>
<pathelement location="${build.classes.dir}"/>
<pathelement location="${build.test.dir}/classes"/>
<fileset dir="archive" includes="*.jar"/>
<pathelement location="lib/junit_4/junit-4.11.jar"/>
<pathelement location="lib/junit_4/hamcrest-core-1.3.jar"/>
</classpath>

<formatter type="plain" /> <!-- to file -->
<batchtest todir="${build.test.dir}/results">
<fileset dir="${src.test.dir}">
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>

<echo>test with unpack of jni library to specific locatiohn</echo>
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
<env key="LC_ALL" value="C"/>
<classpath>
<pathelement location="${build.classes.dir}"/>
<pathelement location="${build.test.dir}/classes"/>
<fileset dir="archive" includes="*.jar"/>
<pathelement location="lib/junit_4/junit-4.11.jar"/>
<pathelement location="lib/junit_4/hamcrest-core-1.3.jar"/>
</classpath>
<sysproperty key="jffi.extract.name" value=""/>

<formatter type="plain" /> <!-- to file -->
<batchtest todir="${build.test.dir}/results">
<fileset dir="${src.test.dir}">
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/kenai/jffi/Init.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private static List<ClassLoader> getClassLoaders() {
}

private static UnsatisfiedLinkError newLoadError(Throwable cause) {
UnsatisfiedLinkError error = new UnsatisfiedLinkError();
UnsatisfiedLinkError error = new UnsatisfiedLinkError(cause.getLocalizedMessage());
error.initCause(cause);

return error;
Expand Down
149 changes: 124 additions & 25 deletions src/main/java/com/kenai/jffi/internal/StubLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -67,14 +71,25 @@ public class StubLoader {
private static volatile Throwable failureCause = null;
private static volatile boolean loaded = false;
private static final File jffiExtractDir;
private static final String jffiExtractName;

private static final String JFFI_EXTRACT_DIR = "jffi.extract.dir";
private static final String JFFI_EXTRACT_NAME = "jffi.extract.name";

static {
String extractDir = System.getProperty("jffi.extract.dir");
String extractDir = System.getProperty(JFFI_EXTRACT_DIR);
if (extractDir != null) {
jffiExtractDir = new File(extractDir);
} else {
jffiExtractDir = null;
}

String extractName = System.getProperty(JFFI_EXTRACT_NAME);
if (extractName != null) {
jffiExtractName = extractName;
} else {
jffiExtractName = null;
}
}

public static final boolean isLoaded() {
Expand Down Expand Up @@ -286,19 +301,27 @@ static void load() {
try {
loadFromJar(jffiExtractDir);
return;
} catch (SecurityException se) {
throw se;
} catch (Throwable t1) {
throw new UnsatisfiedLinkError("could not load jffi library from " + jffiExtractDir);
UnsatisfiedLinkError ule = new UnsatisfiedLinkError("could not load jffi library from " + jffiExtractDir);
ule.initCause(t1);
throw ule;
}
}

// try default tmp location with failover to current directory
try {
loadFromJar(null);
return;
} catch (SecurityException se) {
throw se;
} catch (Throwable t) {
try{
try {
loadFromJar(new File(System.getProperty("user.dir")));
}catch (Throwable t1){
} catch (SecurityException se) {
throw se;
} catch (Throwable t1){
errors.add(t1);
}
}
Expand Down Expand Up @@ -384,7 +407,7 @@ private static boolean loadFromBootPath(String libName, String bootPath, Collect
return false;
}

private static String dlExtension() {
static String dlExtension() {
switch (getOS()) {
case WINDOWS:
return "dll";
Expand All @@ -396,44 +419,120 @@ private static String dlExtension() {
}

private static void loadFromJar(File tmpDirFile) throws IOException, LinkageError {
InputStream is = getStubLibraryStream();
File dstFile;

// Install the stub library to a temporary location
try {
// Install the stub library
String jffiExtractName = StubLoader.jffiExtractName;

// Create tempfile.
dstFile = null == tmpDirFile ? File.createTempFile("jffi", "." + dlExtension()):
File.createTempFile("jffi", "." + dlExtension(), tmpDirFile);
dstFile.deleteOnExit();
try (InputStream sourceIS = getStubLibraryStream()) {
dstFile = calculateExtractPath(tmpDirFile, jffiExtractName);

// Write the library to the tempfile
FileOutputStream os = new FileOutputStream(dstFile);
try {
ReadableByteChannel srcChannel = Channels.newChannel(is);

for (long pos = 0; is.available() > 0; ) {
pos += os.getChannel().transferFrom(srcChannel, pos, Math.max(4096, is.available()));
}
} finally {
os.close();
if (jffiExtractName != null && dstFile.exists()) {
// unpacking to a specific name and that file exists, verify it
verifyExistingLibrary(dstFile, sourceIS);
} else {
unpackLibrary(dstFile, sourceIS);
}
} catch (IOException ioe) {
// If we get here it means we are unable to write the stub library to the system default temp location.
throw tempReadonlyError(ioe);
} finally {
is.close();
}

try {
System.load(dstFile.getAbsolutePath());
dstFile.delete();
if (null == jffiExtractName) dstFile.delete();
} catch (UnsatisfiedLinkError ule) {
// If we get here it means the file wrote to temp ok but can't be loaded from there.
throw tempLoadError(ule);
}
}

private static void unpackLibrary(File dstFile, InputStream sourceIS) throws IOException {
// Write the library to the tempfile
try (FileOutputStream os = new FileOutputStream(dstFile)) {
ReadableByteChannel srcChannel = Channels.newChannel(sourceIS);

for (long pos = 0; sourceIS.available() > 0; ) {
pos += os.getChannel().transferFrom(srcChannel, pos, Math.max(4096, sourceIS.available()));
}
}
}

private static void verifyExistingLibrary(File dstFile, InputStream sourceIS) throws IOException {
int sourceSize = sourceIS.available();

try (FileInputStream targetIS = new FileInputStream(dstFile)) {
// perform minimal verification of the found file
int targetSize = targetIS.available();
if (targetSize != sourceSize) throw sizeMismatchError(dstFile, sourceSize, targetSize);

// compare sha-256 for existing file
MessageDigest sourceMD = MessageDigest.getInstance("SHA-256");
MessageDigest targetMD = MessageDigest.getInstance("SHA-256");
DigestInputStream sourceDIS = new DigestInputStream(sourceIS, sourceMD);
DigestInputStream targetDIS = new DigestInputStream(targetIS, targetMD);
byte[] buf = new byte[8192];
while (sourceIS.available() > 0) {
sourceDIS.read(buf);
targetDIS.read(buf);
}
byte[] sourceDigest = sourceMD.digest();
byte[] targetDigest = targetMD.digest();

if (!Arrays.equals(sourceDigest, targetDigest)) throw digestMismatchError(dstFile);
} catch (NoSuchAlgorithmException nsae) {
throw new IOException(nsae);
}
}

private static SecurityException sizeMismatchError(File dstFile, int sourceSize, int targetSize) {
return new SecurityException("file size mismatch: " + dstFile + " (" + targetSize + ") does not match packaged library (" + sourceSize + ")");
}

private static SecurityException digestMismatchError(File dstFile) {
return new SecurityException("digest mismatch: " + dstFile + " does not match packaged library");
}

static File calculateExtractPath(File tmpDirFile, String jffiExtractName) throws IOException {
if (jffiExtractName == null) return calculateExtractPath(tmpDirFile);

File dstFile;

// allow empty name to mean "jffi-#.#"
if (jffiExtractName.isEmpty()) {
jffiExtractName = "jffi-" + VERSION_MAJOR + "." + VERSION_MINOR;
}

// add extension if necessary
if (!jffiExtractName.endsWith(dlExtension())) {
jffiExtractName = jffiExtractName + "." + dlExtension();
}

// use tmpdir if no dir was specified
if (null == tmpDirFile) {
dstFile = new File(TMPDIR, jffiExtractName);
} else {
dstFile = new File(tmpDirFile, jffiExtractName);
}

return dstFile;
}

static File calculateExtractPath(File tmpDirFile) throws IOException {
File dstFile;

// create tempfile
if (null == tmpDirFile) {
dstFile = File.createTempFile("jffi", "." + dlExtension());
} else {
dstFile = File.createTempFile("jffi", "." + dlExtension(), tmpDirFile);
}

dstFile.deleteOnExit();

return dstFile;
}

private static IOException tempReadonlyError(IOException ioe) {
return new IOException(
TMPDIR_WRITE_ERROR + " " +
Expand Down
11 changes: 0 additions & 11 deletions src/test/java/com/kenai/jffi/ForeignTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,6 @@ public void setUp() {
public void tearDown() {
}

// TODO add test methods here.
// The methods must be annotated with annotation @Test. For example:
//
// @Test
// public void hello() {}
@Test public void version() {
final int VERSION = Foreign.VERSION_MAJOR << 16 | Foreign.VERSION_MINOR << 8 | Foreign.VERSION_MICRO;
int version = Foreign.getInstance().getVersion();
assertEquals("Bad version", VERSION, version);
}

@Test public void pageSize() {
long pageSize = Foreign.getInstance().pageSize();
assertNotSame("Invalid page size", 0, pageSize);
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/com/kenai/jffi/internal/StubLoaderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.kenai.jffi.internal;

import com.kenai.jffi.internal.StubLoader;
import org.junit.Assert;
import org.junit.Test;

import java.io.File;

public class StubLoaderTest {
@Test
public void testExtractName() throws Throwable {
String barName = "bar";
String barFile = "bar." + StubLoader.dlExtension();

File path = StubLoader.calculateExtractPath(new File("foo"), barName);

Assert.assertEquals("foo", path.getParent());
Assert.assertEquals(barFile, path.getName());

path = StubLoader.calculateExtractPath(new File("foo"), barFile);

Assert.assertEquals("foo", path.getParent());
Assert.assertEquals(barFile, path.getName());
}

@Test
public void testDefaultExtractName() throws Throwable {
String defaultFile = "jffi-" + StubLoader.VERSION_MAJOR + "." + StubLoader.VERSION_MINOR + "." + StubLoader.dlExtension();

File path = StubLoader.calculateExtractPath(new File("foo"), "");

Assert.assertEquals("foo", path.getParent());
Assert.assertEquals(
defaultFile,
path.getName());
}
}