From 07f5fb5078a371bcb44b0a81680b328da57e175f Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 3 Oct 2023 15:55:47 +0200 Subject: [PATCH] Customize creation mode for custom RedirectPipe instances --- .../org/jline/terminal/TerminalBuilder.java | 9 ++ .../impl/exec/ExecTerminalProvider.java | 97 ++++++++++++++++--- 2 files changed, 92 insertions(+), 14 deletions(-) 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); + } } }