diff --git a/native/src/main/java/org/jline/nativ/JLineLibrary.java b/native/src/main/java/org/jline/nativ/JLineLibrary.java index ff1adaa3f..54e002b76 100644 --- a/native/src/main/java/org/jline/nativ/JLineLibrary.java +++ b/native/src/main/java/org/jline/nativ/JLineLibrary.java @@ -23,4 +23,6 @@ public class JLineLibrary { } public static native FileDescriptor newFileDescriptor(int fd); + + public static native ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd); } diff --git a/native/src/main/native/jlinenative.c b/native/src/main/native/jlinenative.c index 3c3b6489f..7e8711282 100644 --- a/native/src/main/native/jlinenative.c +++ b/native/src/main/native/jlinenative.c @@ -38,4 +38,26 @@ JNIEXPORT jobject JNICALL JLineLibrary_NATIVE(newFileDescriptor)(JNIEnv *env, jc return ret; } +JNIEXPORT jobject JNICALL JLineLibrary_NATIVE(newRedirectPipe)(JNIEnv *env, jclass that, jobject fd) +{ + jfieldID field_fd; + jmethodID const_rpi; + jclass class_rpi; + jobject ret; + class_rpi = (*env)->FindClass(env, "java/lang/ProcessBuilder$RedirectPipeImpl"); + if (class_rpi == NULL) return NULL; + + // construct a new RedirectPipeImpl + const_rpi = (*env)->GetMethodID(env, class_rpi, "", "()V"); + if (const_rpi == NULL) return NULL; + ret = (*env)->NewObject(env, class_rpi, const_rpi); + + // poke the "fd" field with the file descriptor + field_fd = (*env)->GetFieldID(env, class_rpi, "fd", "Ljava/io/FileDescriptor;"); + if (field_fd == NULL) return NULL; + (*env)->SetObjectField(env, ret, field_fd, fd); + + // and return it + return ret; +} diff --git a/native/src/main/resources/org/jline/nativ/FreeBSD/x86/libjlinenative.so b/native/src/main/resources/org/jline/nativ/FreeBSD/x86/libjlinenative.so index de13483f3..9ad59f06b 100755 Binary files a/native/src/main/resources/org/jline/nativ/FreeBSD/x86/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/FreeBSD/x86/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/FreeBSD/x86_64/libjlinenative.so b/native/src/main/resources/org/jline/nativ/FreeBSD/x86_64/libjlinenative.so index 69a8887da..db84a0835 100755 Binary files a/native/src/main/resources/org/jline/nativ/FreeBSD/x86_64/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/FreeBSD/x86_64/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/arm/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/arm/libjlinenative.so index 9e5f06527..f67f1e324 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/arm/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/arm/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/arm64/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/arm64/libjlinenative.so index 0a3d96b7c..f6b05768f 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/arm64/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/arm64/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/armv6/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/armv6/libjlinenative.so index f31f654a7..189d638e4 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/armv6/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/armv6/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/armv7/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/armv7/libjlinenative.so index c71c24eab..841b5e54f 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/armv7/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/armv7/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/ppc64/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/ppc64/libjlinenative.so index 6b146b8e8..9c06e44d0 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/ppc64/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/ppc64/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/x86/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/x86/libjlinenative.so index 4dba96cd9..ff8aef34d 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/x86/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/x86/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Linux/x86_64/libjlinenative.so b/native/src/main/resources/org/jline/nativ/Linux/x86_64/libjlinenative.so index 826ebf071..918a45b3b 100755 Binary files a/native/src/main/resources/org/jline/nativ/Linux/x86_64/libjlinenative.so and b/native/src/main/resources/org/jline/nativ/Linux/x86_64/libjlinenative.so differ diff --git a/native/src/main/resources/org/jline/nativ/Mac/arm64/libjlinenative.jnilib b/native/src/main/resources/org/jline/nativ/Mac/arm64/libjlinenative.jnilib index 0f051c4af..b968d5e5a 100755 Binary files a/native/src/main/resources/org/jline/nativ/Mac/arm64/libjlinenative.jnilib and b/native/src/main/resources/org/jline/nativ/Mac/arm64/libjlinenative.jnilib differ diff --git a/native/src/main/resources/org/jline/nativ/Mac/x86/libjlinenative.jnilib b/native/src/main/resources/org/jline/nativ/Mac/x86/libjlinenative.jnilib index 49c97f9cb..e1c953ffa 100755 Binary files a/native/src/main/resources/org/jline/nativ/Mac/x86/libjlinenative.jnilib and b/native/src/main/resources/org/jline/nativ/Mac/x86/libjlinenative.jnilib differ diff --git a/native/src/main/resources/org/jline/nativ/Mac/x86_64/libjlinenative.jnilib b/native/src/main/resources/org/jline/nativ/Mac/x86_64/libjlinenative.jnilib index 8eb34c2b3..d7f243142 100755 Binary files a/native/src/main/resources/org/jline/nativ/Mac/x86_64/libjlinenative.jnilib and b/native/src/main/resources/org/jline/nativ/Mac/x86_64/libjlinenative.jnilib differ diff --git a/native/src/main/resources/org/jline/nativ/Windows/x86/jlinenative.dll b/native/src/main/resources/org/jline/nativ/Windows/x86/jlinenative.dll index 74829f10a..10ddc9c48 100755 Binary files a/native/src/main/resources/org/jline/nativ/Windows/x86/jlinenative.dll and b/native/src/main/resources/org/jline/nativ/Windows/x86/jlinenative.dll differ diff --git a/native/src/main/resources/org/jline/nativ/Windows/x86_64/jlinenative.dll b/native/src/main/resources/org/jline/nativ/Windows/x86_64/jlinenative.dll index 9ed75a8d3..28e694811 100755 Binary files a/native/src/main/resources/org/jline/nativ/Windows/x86_64/jlinenative.dll and b/native/src/main/resources/org/jline/nativ/Windows/x86_64/jlinenative.dll differ diff --git a/native/src/test/java/org/jline/nativ/JLineLibraryTest.java b/native/src/test/java/org/jline/nativ/JLineLibraryTest.java index 7aab3ae92..9b841bdac 100644 --- a/native/src/test/java/org/jline/nativ/JLineLibraryTest.java +++ b/native/src/test/java/org/jline/nativ/JLineLibraryTest.java @@ -26,4 +26,16 @@ void testNewFileDescriptor() throws Exception { field.setAccessible(true); assertEquals(12, field.get(fd), fd.toString()); } + + @Test + void testNewRedirectPipeImpl() throws Exception { + ProcessBuilder.Redirect redirect = JLineLibrary.newRedirectPipe(FileDescriptor.out); + assertNotNull(redirect); + // This requires '--add-opens java.base/java.lang=ALL-UNNAMED', but adding this option + // defeats the very purpose of the method + // + // Method mth = redirect.getClass().getDeclaredMethod("getFd"); + // mth.setAccessible(true); + // assertEquals(System.out, mth.invoke(redirect), redirect.toString()); + } } diff --git a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java index dc57b1802..8797f7845 100644 --- a/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java +++ b/terminal/src/main/java/org/jline/terminal/TerminalBuilder.java @@ -79,6 +79,15 @@ public final class TerminalBuilder { public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE_REFLECTION = "reflection"; public static final String PROP_FILE_DESCRIPTOR_CREATION_MODE_DEFAULT = "reflection,native"; + // + // System properties controlling how RedirectPipe are created. + // The value can be a comma separated list of defined mechanisms. + // + public static final String PROP_REDIRECT_PIPE_CREATION_MODE = "org.jline.terminal.exec.redirectPipeCreationMode"; + public static final String PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE = "native"; + public static final String PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION = "reflection"; + public static final String PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT = "reflection,native"; + // // Terminal output control // diff --git a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java index ed6c9e646..61b19cc77 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java +++ b/terminal/src/main/java/org/jline/terminal/impl/exec/ExecTerminalProvider.java @@ -16,6 +16,8 @@ import java.lang.reflect.Field; import java.nio.charset.Charset; +import org.jline.nativ.JLineLibrary; +import org.jline.nativ.JLineNativeLoader; import org.jline.terminal.Attributes; import org.jline.terminal.Size; import org.jline.terminal.Terminal; @@ -28,6 +30,11 @@ import org.jline.utils.Log; import org.jline.utils.OSUtils; +import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE; +import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT; +import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE; +import static org.jline.terminal.TerminalBuilder.PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION; + public class ExecTerminalProvider implements TerminalProvider { private static boolean warned; @@ -137,7 +144,7 @@ public String systemStreamName(Stream stream) { try { ProcessBuilder.Redirect input = stream == Stream.Input ? ProcessBuilder.Redirect.INHERIT - : getRedirect(stream == Stream.Output ? FileDescriptor.out : FileDescriptor.err); + : newDescriptor(stream == Stream.Output ? FileDescriptor.out : FileDescriptor.err); Process p = new ProcessBuilder(OSUtils.TTY_COMMAND).redirectInput(input).start(); String result = ExecHelper.waitAndCapture(p); @@ -157,20 +164,82 @@ public String systemStreamName(Stream stream) { return null; } + private static RedirectPipeCreator redirectPipeCreator; + + protected static ProcessBuilder.Redirect newDescriptor(FileDescriptor fd) { + if (redirectPipeCreator == null) { + String str = System.getProperty(PROP_REDIRECT_PIPE_CREATION_MODE, PROP_REDIRECT_PIPE_CREATION_MODE_DEFAULT); + String[] modes = str.split(","); + IllegalStateException ise = new IllegalStateException("Unable to create RedirectPipe"); + for (String mode : modes) { + try { + switch (mode) { + case PROP_REDIRECT_PIPE_CREATION_MODE_NATIVE: + redirectPipeCreator = new NativeRedirectPipeCreator(); + break; + case PROP_REDIRECT_PIPE_CREATION_MODE_REFLECTION: + redirectPipeCreator = new ReflectionRedirectPipeCreator(); + break; + } + } catch (Throwable t) { + // ignore + ise.addSuppressed(t); + } + if (redirectPipeCreator != null) { + break; + } + } + if (redirectPipeCreator == null) { + throw ise; + } + } + return redirectPipeCreator.newRedirectPipe(fd); + } + + interface RedirectPipeCreator { + ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd); + } + /** - * This requires --add-opens java.base/java.lang=ALL-UNNAMED + * Reflection based file descriptor creator. + * This requires the following option + * --add-opens java.base/java.lang=ALL-UNNAMED */ - private ProcessBuilder.Redirect getRedirect(FileDescriptor fd) throws ReflectiveOperationException { - // This is not really allowed, but this is the only way to redirect the output or error stream - // to the input. This is definitely not something you'd usually want to do, but in the case of - // the `tty` utility, it provides a way to get - Class rpi = Class.forName("java.lang.ProcessBuilder$RedirectPipeImpl"); - Constructor cns = rpi.getDeclaredConstructor(); - cns.setAccessible(true); - ProcessBuilder.Redirect input = (ProcessBuilder.Redirect) cns.newInstance(); - Field f = rpi.getDeclaredField("fd"); - f.setAccessible(true); - f.set(input, fd); - return input; + static class ReflectionRedirectPipeCreator implements RedirectPipeCreator { + private final Constructor constructor; + private final Field fdField; + + @SuppressWarnings("unchecked") + ReflectionRedirectPipeCreator() throws Exception { + Class rpi = Class.forName("java.lang.ProcessBuilder$RedirectPipeImpl"); + constructor = (Constructor) rpi.getDeclaredConstructor(); + constructor.setAccessible(true); + fdField = rpi.getDeclaredField("fd"); + fdField.setAccessible(true); + } + + @Override + public ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd) { + try { + ProcessBuilder.Redirect input = constructor.newInstance(); + fdField.set(input, fd); + return input; + } catch (ReflectiveOperationException e) { + // This should not happen as the field has been set accessible + throw new IllegalStateException(e); + } + } + } + + static class NativeRedirectPipeCreator implements RedirectPipeCreator { + public NativeRedirectPipeCreator() { + // Force load the library + JLineNativeLoader.initialize(); + } + + @Override + public ProcessBuilder.Redirect newRedirectPipe(FileDescriptor fd) { + return JLineLibrary.newRedirectPipe(fd); + } } }