Skip to content

Commit 02c3718

Browse files
authored
Merge pull request #99 from headius/specific_extract_file
Add jffi.extract.name for specific file
2 parents 92cefde + bfd6e5e commit 02c3718

File tree

5 files changed

+202
-37
lines changed

5 files changed

+202
-37
lines changed

build.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@
161161

162162
<target name="test" depends="compile,-compile-test">
163163
<mkdir dir="${build.test.dir}/results"/>
164+
165+
<echo>test with jni libraries from sources</echo>
164166
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
165167
<env key="LC_ALL" value="C"/>
166168
<classpath>
@@ -177,7 +179,45 @@
177179
<include name="**/*Test*.java"/>
178180
</fileset>
179181
</batchtest>
182+
</junit>
183+
184+
<echo>test with unpack of jni library</echo>
185+
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
186+
<env key="LC_ALL" value="C"/>
187+
<classpath>
188+
<pathelement location="${build.classes.dir}"/>
189+
<pathelement location="${build.test.dir}/classes"/>
190+
<fileset dir="archive" includes="*.jar"/>
191+
<pathelement location="lib/junit_4/junit-4.11.jar"/>
192+
<pathelement location="lib/junit_4/hamcrest-core-1.3.jar"/>
193+
</classpath>
194+
195+
<formatter type="plain" /> <!-- to file -->
196+
<batchtest todir="${build.test.dir}/results">
197+
<fileset dir="${src.test.dir}">
198+
<include name="**/*Test*.java"/>
199+
</fileset>
200+
</batchtest>
201+
</junit>
180202

203+
<echo>test with unpack of jni library to specific locatiohn</echo>
204+
<junit fork="${test.fork}" forkMode="${test.fork.mode}" printsummary="withOutAndErr" haltonfailure="true">
205+
<env key="LC_ALL" value="C"/>
206+
<classpath>
207+
<pathelement location="${build.classes.dir}"/>
208+
<pathelement location="${build.test.dir}/classes"/>
209+
<fileset dir="archive" includes="*.jar"/>
210+
<pathelement location="lib/junit_4/junit-4.11.jar"/>
211+
<pathelement location="lib/junit_4/hamcrest-core-1.3.jar"/>
212+
</classpath>
213+
<sysproperty key="jffi.extract.name" value=""/>
214+
215+
<formatter type="plain" /> <!-- to file -->
216+
<batchtest todir="${build.test.dir}/results">
217+
<fileset dir="${src.test.dir}">
218+
<include name="**/*Test*.java"/>
219+
</fileset>
220+
</batchtest>
181221
</junit>
182222
</target>
183223

src/main/java/com/kenai/jffi/Init.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private static List<ClassLoader> getClassLoaders() {
129129
}
130130

131131
private static UnsatisfiedLinkError newLoadError(Throwable cause) {
132-
UnsatisfiedLinkError error = new UnsatisfiedLinkError();
132+
UnsatisfiedLinkError error = new UnsatisfiedLinkError(cause.getLocalizedMessage());
133133
error.initCause(cause);
134134

135135
return error;

src/main/java/com/kenai/jffi/internal/StubLoader.java

Lines changed: 124 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222

2323
import java.io.CharArrayWriter;
2424
import java.io.File;
25+
import java.io.FileInputStream;
2526
import java.io.FileOutputStream;
2627
import java.io.IOException;
2728
import java.io.InputStream;
2829
import java.io.PrintWriter;
2930
import java.nio.channels.Channels;
3031
import java.nio.channels.ReadableByteChannel;
32+
import java.security.DigestInputStream;
33+
import java.security.MessageDigest;
34+
import java.security.NoSuchAlgorithmException;
3135
import java.util.ArrayList;
3236
import java.util.Arrays;
3337
import java.util.Collection;
@@ -67,14 +71,25 @@ public class StubLoader {
6771
private static volatile Throwable failureCause = null;
6872
private static volatile boolean loaded = false;
6973
private static final File jffiExtractDir;
74+
private static final String jffiExtractName;
75+
76+
private static final String JFFI_EXTRACT_DIR = "jffi.extract.dir";
77+
private static final String JFFI_EXTRACT_NAME = "jffi.extract.name";
7078

7179
static {
72-
String extractDir = System.getProperty("jffi.extract.dir");
80+
String extractDir = System.getProperty(JFFI_EXTRACT_DIR);
7381
if (extractDir != null) {
7482
jffiExtractDir = new File(extractDir);
7583
} else {
7684
jffiExtractDir = null;
7785
}
86+
87+
String extractName = System.getProperty(JFFI_EXTRACT_NAME);
88+
if (extractName != null) {
89+
jffiExtractName = extractName;
90+
} else {
91+
jffiExtractName = null;
92+
}
7893
}
7994

8095
public static final boolean isLoaded() {
@@ -286,19 +301,27 @@ static void load() {
286301
try {
287302
loadFromJar(jffiExtractDir);
288303
return;
304+
} catch (SecurityException se) {
305+
throw se;
289306
} catch (Throwable t1) {
290-
throw new UnsatisfiedLinkError("could not load jffi library from " + jffiExtractDir);
307+
UnsatisfiedLinkError ule = new UnsatisfiedLinkError("could not load jffi library from " + jffiExtractDir);
308+
ule.initCause(t1);
309+
throw ule;
291310
}
292311
}
293312

294313
// try default tmp location with failover to current directory
295314
try {
296315
loadFromJar(null);
297316
return;
317+
} catch (SecurityException se) {
318+
throw se;
298319
} catch (Throwable t) {
299-
try{
320+
try {
300321
loadFromJar(new File(System.getProperty("user.dir")));
301-
}catch (Throwable t1){
322+
} catch (SecurityException se) {
323+
throw se;
324+
} catch (Throwable t1){
302325
errors.add(t1);
303326
}
304327
}
@@ -384,7 +407,7 @@ private static boolean loadFromBootPath(String libName, String bootPath, Collect
384407
return false;
385408
}
386409

387-
private static String dlExtension() {
410+
static String dlExtension() {
388411
switch (getOS()) {
389412
case WINDOWS:
390413
return "dll";
@@ -396,44 +419,120 @@ private static String dlExtension() {
396419
}
397420

398421
private static void loadFromJar(File tmpDirFile) throws IOException, LinkageError {
399-
InputStream is = getStubLibraryStream();
400422
File dstFile;
401423

402-
// Install the stub library to a temporary location
403-
try {
424+
// Install the stub library
425+
String jffiExtractName = StubLoader.jffiExtractName;
404426

405-
// Create tempfile.
406-
dstFile = null == tmpDirFile ? File.createTempFile("jffi", "." + dlExtension()):
407-
File.createTempFile("jffi", "." + dlExtension(), tmpDirFile);
408-
dstFile.deleteOnExit();
427+
try (InputStream sourceIS = getStubLibraryStream()) {
428+
dstFile = calculateExtractPath(tmpDirFile, jffiExtractName);
409429

410-
// Write the library to the tempfile
411-
FileOutputStream os = new FileOutputStream(dstFile);
412-
try {
413-
ReadableByteChannel srcChannel = Channels.newChannel(is);
414-
415-
for (long pos = 0; is.available() > 0; ) {
416-
pos += os.getChannel().transferFrom(srcChannel, pos, Math.max(4096, is.available()));
417-
}
418-
} finally {
419-
os.close();
430+
if (jffiExtractName != null && dstFile.exists()) {
431+
// unpacking to a specific name and that file exists, verify it
432+
verifyExistingLibrary(dstFile, sourceIS);
433+
} else {
434+
unpackLibrary(dstFile, sourceIS);
420435
}
421436
} catch (IOException ioe) {
422437
// If we get here it means we are unable to write the stub library to the system default temp location.
423438
throw tempReadonlyError(ioe);
424-
} finally {
425-
is.close();
426439
}
427440

428441
try {
429442
System.load(dstFile.getAbsolutePath());
430-
dstFile.delete();
443+
if (null == jffiExtractName) dstFile.delete();
431444
} catch (UnsatisfiedLinkError ule) {
432445
// If we get here it means the file wrote to temp ok but can't be loaded from there.
433446
throw tempLoadError(ule);
434447
}
435448
}
436449

450+
private static void unpackLibrary(File dstFile, InputStream sourceIS) throws IOException {
451+
// Write the library to the tempfile
452+
try (FileOutputStream os = new FileOutputStream(dstFile)) {
453+
ReadableByteChannel srcChannel = Channels.newChannel(sourceIS);
454+
455+
for (long pos = 0; sourceIS.available() > 0; ) {
456+
pos += os.getChannel().transferFrom(srcChannel, pos, Math.max(4096, sourceIS.available()));
457+
}
458+
}
459+
}
460+
461+
private static void verifyExistingLibrary(File dstFile, InputStream sourceIS) throws IOException {
462+
int sourceSize = sourceIS.available();
463+
464+
try (FileInputStream targetIS = new FileInputStream(dstFile)) {
465+
// perform minimal verification of the found file
466+
int targetSize = targetIS.available();
467+
if (targetSize != sourceSize) throw sizeMismatchError(dstFile, sourceSize, targetSize);
468+
469+
// compare sha-256 for existing file
470+
MessageDigest sourceMD = MessageDigest.getInstance("SHA-256");
471+
MessageDigest targetMD = MessageDigest.getInstance("SHA-256");
472+
DigestInputStream sourceDIS = new DigestInputStream(sourceIS, sourceMD);
473+
DigestInputStream targetDIS = new DigestInputStream(targetIS, targetMD);
474+
byte[] buf = new byte[8192];
475+
while (sourceIS.available() > 0) {
476+
sourceDIS.read(buf);
477+
targetDIS.read(buf);
478+
}
479+
byte[] sourceDigest = sourceMD.digest();
480+
byte[] targetDigest = targetMD.digest();
481+
482+
if (!Arrays.equals(sourceDigest, targetDigest)) throw digestMismatchError(dstFile);
483+
} catch (NoSuchAlgorithmException nsae) {
484+
throw new IOException(nsae);
485+
}
486+
}
487+
488+
private static SecurityException sizeMismatchError(File dstFile, int sourceSize, int targetSize) {
489+
return new SecurityException("file size mismatch: " + dstFile + " (" + targetSize + ") does not match packaged library (" + sourceSize + ")");
490+
}
491+
492+
private static SecurityException digestMismatchError(File dstFile) {
493+
return new SecurityException("digest mismatch: " + dstFile + " does not match packaged library");
494+
}
495+
496+
static File calculateExtractPath(File tmpDirFile, String jffiExtractName) throws IOException {
497+
if (jffiExtractName == null) return calculateExtractPath(tmpDirFile);
498+
499+
File dstFile;
500+
501+
// allow empty name to mean "jffi-#.#"
502+
if (jffiExtractName.isEmpty()) {
503+
jffiExtractName = "jffi-" + VERSION_MAJOR + "." + VERSION_MINOR;
504+
}
505+
506+
// add extension if necessary
507+
if (!jffiExtractName.endsWith(dlExtension())) {
508+
jffiExtractName = jffiExtractName + "." + dlExtension();
509+
}
510+
511+
// use tmpdir if no dir was specified
512+
if (null == tmpDirFile) {
513+
dstFile = new File(TMPDIR, jffiExtractName);
514+
} else {
515+
dstFile = new File(tmpDirFile, jffiExtractName);
516+
}
517+
518+
return dstFile;
519+
}
520+
521+
static File calculateExtractPath(File tmpDirFile) throws IOException {
522+
File dstFile;
523+
524+
// create tempfile
525+
if (null == tmpDirFile) {
526+
dstFile = File.createTempFile("jffi", "." + dlExtension());
527+
} else {
528+
dstFile = File.createTempFile("jffi", "." + dlExtension(), tmpDirFile);
529+
}
530+
531+
dstFile.deleteOnExit();
532+
533+
return dstFile;
534+
}
535+
437536
private static IOException tempReadonlyError(IOException ioe) {
438537
return new IOException(
439538
TMPDIR_WRITE_ERROR + " " +

src/test/java/com/kenai/jffi/ForeignTest.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,6 @@ public void setUp() {
3333
public void tearDown() {
3434
}
3535

36-
// TODO add test methods here.
37-
// The methods must be annotated with annotation @Test. For example:
38-
//
39-
// @Test
40-
// public void hello() {}
41-
@Test public void version() {
42-
final int VERSION = Foreign.VERSION_MAJOR << 16 | Foreign.VERSION_MINOR << 8 | Foreign.VERSION_MICRO;
43-
int version = Foreign.getInstance().getVersion();
44-
assertEquals("Bad version", VERSION, version);
45-
}
46-
4736
@Test public void pageSize() {
4837
long pageSize = Foreign.getInstance().pageSize();
4938
assertNotSame("Invalid page size", 0, pageSize);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.kenai.jffi.internal;
2+
3+
import com.kenai.jffi.internal.StubLoader;
4+
import org.junit.Assert;
5+
import org.junit.Test;
6+
7+
import java.io.File;
8+
9+
public class StubLoaderTest {
10+
@Test
11+
public void testExtractName() throws Throwable {
12+
String barName = "bar";
13+
String barFile = "bar." + StubLoader.dlExtension();
14+
15+
File path = StubLoader.calculateExtractPath(new File("foo"), barName);
16+
17+
Assert.assertEquals("foo", path.getParent());
18+
Assert.assertEquals(barFile, path.getName());
19+
20+
path = StubLoader.calculateExtractPath(new File("foo"), barFile);
21+
22+
Assert.assertEquals("foo", path.getParent());
23+
Assert.assertEquals(barFile, path.getName());
24+
}
25+
26+
@Test
27+
public void testDefaultExtractName() throws Throwable {
28+
String defaultFile = "jffi-" + StubLoader.VERSION_MAJOR + "." + StubLoader.VERSION_MINOR + "." + StubLoader.dlExtension();
29+
30+
File path = StubLoader.calculateExtractPath(new File("foo"), "");
31+
32+
Assert.assertEquals("foo", path.getParent());
33+
Assert.assertEquals(
34+
defaultFile,
35+
path.getName());
36+
}
37+
}

0 commit comments

Comments
 (0)