Skip to content

Commit 193f414

Browse files
committed
Enhance support for predictable lib name
* New: specify blank name (-Djffi.extract.name) to use default name of "jffi-#.#.ext" with current jffi version and platform- specific ext. * New: existing library will be verified using SHA-256 hash of library contained in jffi jars.
1 parent f83e55c commit 193f414

File tree

3 files changed

+122
-40
lines changed

3 files changed

+122
-40
lines changed

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: 99 additions & 37 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;
@@ -297,19 +301,27 @@ static void load() {
297301
try {
298302
loadFromJar(jffiExtractDir);
299303
return;
304+
} catch (SecurityException se) {
305+
throw se;
300306
} catch (Throwable t1) {
301-
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;
302310
}
303311
}
304312

305313
// try default tmp location with failover to current directory
306314
try {
307315
loadFromJar(null);
308316
return;
317+
} catch (SecurityException se) {
318+
throw se;
309319
} catch (Throwable t) {
310-
try{
320+
try {
311321
loadFromJar(new File(System.getProperty("user.dir")));
312-
}catch (Throwable t1){
322+
} catch (SecurityException se) {
323+
throw se;
324+
} catch (Throwable t1){
313325
errors.add(t1);
314326
}
315327
}
@@ -395,7 +407,7 @@ private static boolean loadFromBootPath(String libName, String bootPath, Collect
395407
return false;
396408
}
397409

398-
private static String dlExtension() {
410+
static String dlExtension() {
399411
switch (getOS()) {
400412
case WINDOWS:
401413
return "dll";
@@ -407,35 +419,23 @@ private static String dlExtension() {
407419
}
408420

409421
private static void loadFromJar(File tmpDirFile) throws IOException, LinkageError {
410-
InputStream is = getStubLibraryStream();
411422
File dstFile;
412423

413-
// Install the stub library to a temporary location
424+
// Install the stub library
414425
String jffiExtractName = StubLoader.jffiExtractName;
415-
try {
426+
427+
try (InputStream sourceIS = getStubLibraryStream()) {
416428
dstFile = calculateExtractPath(tmpDirFile, jffiExtractName);
417429

418-
if (dstFile.exists()) {
419-
// attempt to load existing file
420-
// TODO: verify file matches bundled library
430+
if (jffiExtractName != null && dstFile.exists()) {
431+
// unpacking to a specific name and that file exists, verify it
432+
verifyExistingLibrary(dstFile, sourceIS);
421433
} else {
422-
// Write the library to the tempfile
423-
FileOutputStream os = new FileOutputStream(dstFile);
424-
try {
425-
ReadableByteChannel srcChannel = Channels.newChannel(is);
426-
427-
for (long pos = 0; is.available() > 0; ) {
428-
pos += os.getChannel().transferFrom(srcChannel, pos, Math.max(4096, is.available()));
429-
}
430-
} finally {
431-
os.close();
432-
}
434+
unpackLibrary(dstFile, sourceIS);
433435
}
434436
} catch (IOException ioe) {
435437
// If we get here it means we are unable to write the stub library to the system default temp location.
436438
throw tempReadonlyError(ioe);
437-
} finally {
438-
is.close();
439439
}
440440

441441
try {
@@ -447,25 +447,87 @@ private static void loadFromJar(File tmpDirFile) throws IOException, LinkageErro
447447
}
448448
}
449449

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

src/test/java/com/kenai/jffi/internal/StubLoaderTest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,29 @@
99
public class StubLoaderTest {
1010
@Test
1111
public void testExtractName() throws Throwable {
12-
File path = StubLoader.calculateExtractPath(new File("foo"), "bar");
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"), "");
1331

1432
Assert.assertEquals("foo", path.getParent());
15-
Assert.assertEquals("bar", path.getName());
33+
Assert.assertEquals(
34+
defaultFile,
35+
path.getName());
1636
}
1737
}

0 commit comments

Comments
 (0)