Correctly monkey-patch id also in whe os / arch is used within library name. (#8913)

Motivation:

2bb9f64e16 introduced a change which made it possible to use different shaded versions of netty-tcnative on the classpath. This only partly worked as we did not correctly handled the case when os / arch is part of the library name (which is the case when netty-tcnative-boringssl-static is used with the uber jar).

Modifications:

- If patching the ID failed we retry again with the os / arch stripped
- Add unit tests to verify that patching ID now works with and without os / arch as suffix.

Result:

Using multiple shaded version of netty-tcnative-boringssl-static on MacOS works.
This commit is contained in:
Norman Maurer 2019-03-05 09:10:26 +01:00
parent c6b372f517
commit 6c4d6ae332
2 changed files with 122 additions and 18 deletions

View File

@ -185,28 +185,16 @@ public final class NativeLibraryLoader {
in = url.openStream(); in = url.openStream();
out = new FileOutputStream(tmpFile); out = new FileOutputStream(tmpFile);
if (shouldShadedLibraryIdBePatched(packagePrefix)) {
patchShadedLibraryId(in, out, originalName, name);
} else {
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int length; int length;
if (TRY_TO_PATCH_SHADED_ID && PlatformDependent.isOsx() && !packagePrefix.isEmpty()) {
// We read the whole native lib into memory to make it easier to monkey-patch the id.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(in.available());
while ((length = in.read(buffer)) > 0) {
byteArrayOutputStream.write(buffer, 0, length);
}
byteArrayOutputStream.flush();
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
// Try to patch the library id.
patchShadedLibraryId(bytes, originalName, name);
out.write(bytes);
} else {
while ((length = in.read(buffer)) > 0) { while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length); out.write(buffer, 0, length);
} }
} }
out.flush(); out.flush();
// Close the output stream before loading the unpacked library, // Close the output stream before loading the unpacked library,
@ -250,10 +238,50 @@ public final class NativeLibraryLoader {
} }
} }
// Package-private for testing.
static boolean patchShadedLibraryId(InputStream in, OutputStream out, String originalName, String name)
throws IOException {
byte[] buffer = new byte[8192];
int length;
// We read the whole native lib into memory to make it easier to monkey-patch the id.
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(in.available());
while ((length = in.read(buffer)) > 0) {
byteArrayOutputStream.write(buffer, 0, length);
}
byteArrayOutputStream.flush();
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
final boolean patched;
// Try to patch the library id.
if (!patchShadedLibraryId(bytes, originalName, name)) {
// We did not find the Id, check if we used a originalName that has the os and arch as suffix.
// If this is the case we should also try to patch with the os and arch suffix removed.
String os = PlatformDependent.normalizedOs();
String arch = PlatformDependent.normalizedArch();
String osArch = "_" + os + "_" + arch;
if (originalName.endsWith(osArch)) {
patched = patchShadedLibraryId(bytes,
originalName.substring(0, originalName.length() - osArch.length()), name);
} else {
patched = false;
}
} else {
patched = true;
}
out.write(bytes, 0, bytes.length);
return patched;
}
private static boolean shouldShadedLibraryIdBePatched(String packagePrefix) {
return TRY_TO_PATCH_SHADED_ID && PlatformDependent.isOsx() && !packagePrefix.isEmpty();
}
/** /**
* Try to patch shaded library to ensure it uses a unique ID. * Try to patch shaded library to ensure it uses a unique ID.
*/ */
private static void patchShadedLibraryId(byte[] bytes, String originalName, String name) { private static boolean patchShadedLibraryId(byte[] bytes, String originalName, String name) {
// Our native libs always have the name as part of their id so we can search for it and replace it // Our native libs always have the name as part of their id so we can search for it and replace it
// to make the ID unique if shading is used. // to make the ID unique if shading is used.
byte[] nameBytes = originalName.getBytes(CharsetUtil.UTF_8); byte[] nameBytes = originalName.getBytes(CharsetUtil.UTF_8);
@ -279,6 +307,7 @@ public final class NativeLibraryLoader {
if (idIdx == -1) { if (idIdx == -1) {
logger.debug("Was not able to find the ID of the shaded native library {}, can't adjust it.", name); logger.debug("Was not able to find the ID of the shaded native library {}, can't adjust it.", name);
return false;
} else { } else {
// We found our ID... now monkey-patch it! // We found our ID... now monkey-patch it!
for (int i = 0; i < nameBytes.length; i++) { for (int i = 0; i < nameBytes.length; i++) {
@ -291,6 +320,7 @@ public final class NativeLibraryLoader {
"Found the ID of the shaded native library {}. Replacing ID part {} with {}", "Found the ID of the shaded native library {}. Replacing ID part {} with {}",
name, originalName, new String(bytes, idIdx, nameBytes.length, CharsetUtil.UTF_8)); name, originalName, new String(bytes, idIdx, nameBytes.length, CharsetUtil.UTF_8));
} }
return true;
} }
} }

View File

@ -15,11 +15,19 @@
*/ */
package io.netty.util.internal; package io.netty.util.internal;
import io.netty.util.CharsetUtil;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -61,4 +69,70 @@ public class NativeLibraryLoaderTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Test
public void testPatchingId() throws IOException {
testPatchingId0(true, false);
}
@Test
public void testPatchingIdWithOsArch() throws IOException {
testPatchingId0(true, true);
}
@Test
public void testPatchingIdNotMatch() throws IOException {
testPatchingId0(false, false);
}
@Test
public void testPatchingIdWithOsArchNotMatch() throws IOException {
testPatchingId0(false, true);
}
private static void testPatchingId0(boolean match, boolean withOsArch) throws IOException {
byte[] bytes = new byte[1024];
PlatformDependent.threadLocalRandom().nextBytes(bytes);
byte[] idBytes = ("/workspace/netty-tcnative/boringssl-static/target/" +
"native-build/target/lib/libnetty_tcnative-2.0.20.Final.jnilib").getBytes(CharsetUtil.UTF_8);
String originalName;
if (match) {
originalName = "netty-tcnative";
} else {
originalName = "nonexist_tcnative";
}
String name = "shaded_" + originalName;
if (withOsArch) {
name += "_osx_x86_64";
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(bytes, 0, bytes.length);
out.write(idBytes, 0, idBytes.length);
out.write(bytes, 0 , bytes.length);
out.flush();
byte[] inBytes = out.toByteArray();
out.close();
InputStream inputStream = new ByteArrayInputStream(inBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
assertEquals(match,
NativeLibraryLoader.patchShadedLibraryId(inputStream, outputStream, originalName, name));
outputStream.flush();
byte[] outputBytes = outputStream.toByteArray();
assertArrayEquals(bytes, Arrays.copyOfRange(outputBytes, 0, bytes.length));
byte[] patchedId = Arrays.copyOfRange(outputBytes, bytes.length, bytes.length + idBytes.length);
assertEquals(!match, Arrays.equals(idBytes, patchedId));
assertArrayEquals(bytes,
Arrays.copyOfRange(outputBytes, bytes.length + idBytes.length, outputBytes.length));
assertEquals(inBytes.length, outputBytes.length);
} finally {
inputStream.close();
outputStream.close();
}
}
} }