Access sub-process controlling terminal from Java process - java

I have a long-running Java server application that launches a sub-process to perform a particular task (in this case, extract the contents of a 7zip file using the 7z command-line utility, but that particular detail shouldn't be relevant here).
The server application runs with Java 8 under Ubuntu 14.
The sub-process is being launched via the Java ProcessBuilder API.
The file being accessed by the sub-process might be password-protected.
If the file is password-protected and no password is supplied as a command-line argument, the 7z program will attempt to display a message to the terminal prompting for a password, and then read the password from the terminal.
At this point, the sub-process hangs and will not complete unless I hit <Enter> twice in the terminal that controls the Java process.
The underlying issue seems to be that the 7z utility uses the getpass system call to display the prompt and read user input. According to the docs:
The getpass() function opens /dev/tty (the controlling terminal of the process), outputs the string prompt, turns off echoing, reads one line (the "password"), restores the terminal state and closes /dev/tty again.
Since this is a server application, it is not acceptable to have the sub-process block waiting on input from the Java terminal. What I would like to happen instead is to programmatically shut down input on the sub-process terminal, so that the getpass call returns immediately with no input and the sub-process exits with an error code. Unfortunately all of my attempts at shutting down input to the sub-process terminal have resulted in the same behavior as above:
I tried manually closing the stream returned by Process.getOutputStream() immediately after starting the sub-process.
I tried using ProcessBuilder.redirectInput to redirect sub-process input to read from an empty file, and from /dev/null.
For good measure, I even tried passing Redirect.INHERIT to ProcessBuilder even though that doesn't seem like it's remotely what I want.
It seems like this should be feasible, because I've observed the desired behavior when 7z is launched with the exact same command-line as a sub-process of a daemonized socat process where the terminal input is connected to /dev/null, but I'm at a loss for how to accomplish this in Java using the tools at my disposal.

In order to preclude that the getpass() function opens /dev/tty (the controlling terminal of the process), we have to arrange that the utility has no controlling tty, which is achieved by calling setsid(). This C program (let's name it notty) makes this call and then executes the given utility:
int main(int argc, char *argv[])
{
setsid(); // get rid of the controlling tty
close(0); // preclude reading STDIN as well
execvp(argv[1], argv+1); // execute the given program file
}
We can use it from the Java application by prepending it to the ProcessBuilder command, e. g.:
ProcessBuilder pb = new ProcessBuilder("notty", "7z", … /*arguments*/);

Related

How do you write to a command prompt in Java?

Background
I started a command prompt from a Java application. Now I want to input commands into the command prompt that was just generated. How can you run commands in the prompt that was just generated by the Java program?
What I've tried
My code creates a process that starts the command prompt. And then it gets the process's OutputStream to try and write to it. But I don't see any changes happening. It should just change directories and then run a series of commands in the new directory.
// Block that makes new command prompt
List<String> commands = new ArrayList<String>();
commands.add("cmd.exe");
commands.add("/c");
commands.add("start");
commands.add("cmd.exe");
// Block that creates a writer to write to new command prompt
ProcessBuilder pb = new ProcessBuilder(commands);
Process p = pb.start();
OutputStream os = p.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
// Block that actually writes the commands
writer.write(String.format("cd %s\n", PATH);
writer.write(OTHER_COMMANDS); // I'm ommiting the other commands because there's a lot
writer.flush();
writer.close();
I'm unsure why the commands aren't written to the command prompt that pops up. I see that a new command prompt pops up, but the directory is unchanged from where it starts. How can I input commands into the prompt that I generate?
When you're writing to p, you're writing to this command's standard input:
cmd.exe /c start cmd.exe
You're writing to the first cmd.exe. Which does not do anything except start the second one. It is not possible (at least not simple) to get a handle to the second process. You can use /K, and merge the commands with && after each other. For example:
commands.add("/K");
commands.add(String.join(" && ", OTHER_COMMANDS_AS_LIST));
Your program ProcessBuilder gives you access to the stdin, stdout, and stderr of the process cmd.exe. Those are not the same things as the input and the output of a window and command prompt opened up by the program cmd.exe
There is an answer here regarding how to do it with c#: Create a cmd window and write to it from C# application
In Java I am not aware of a similar API, it might actually require using JNI or JNA in order to gain access to the Windows APIs you would need to use. The function you use (through JNI or JNA) would be the same https://learn.microsoft.com/en-us/windows/console/allocconsole referenced in that other answer. So that is one way to do it, but a full description of how to use either JNI or JNA is outside the scope of an answer here, should you choose to go that route.
As far as I can see, you're trying to implement something vaguely similar to a remote shell (SSH without network and without encryption)...
This should work like you expect for a *nix-like shell, because those shells handle their standard input and output correctly, precisely for the case that they need to be redirected (over a network, or whatever).
The Windows cmd.exe is actually not a pure shell. It is a terminal emulator (the black window that appears on the screen) and the shell, in one process. That's why it doesn't get its input from stdin, and doesn't print its output on stdout. Instead, it listens to GUI keyboard events for input and handles them internally, and the output is displayed directly in the window, without writing it to stdout.
That's why there is no easy way to "remote control" a cmd.exe. It's just not made for that.
You can try this with "proper" shells, like a Cygwin'ed bash.exe, or maybe PowerShell, or similar.
It's actually astonishing that the cmd.exe which was created as a quick'n'dirty "DOS window" some 20 years ago, is still surviving to this day and is actually used for production work...

How to check if a Java application is running within a Terminal window (System.in/System.out are available) [duplicate]

If the input is interactive, i.e. from the console, I want to print a command prompt e.g. ">"
But if it is redirected e.g. from a file, then I do not want to prompt.
System.in is an abstract InputStream which does not appear to have any method for this.
Maybe you could use instanceof if the concrete type is different?
As well, if System.out is redirected to a file I also do not want to prompt
AFAIK, there is no way to do this in pure Java, and even doing it in JNI / JNA would be complicated.
An alternative might be to use the new Console API introduced in JDK 1.6. This allows you to try to get a reader/writer for the console. If it succeeds, the result is guaranteed to be interactive ... in the sense that you mean.
A second alternative would be to do the "check for interactivity" in the wrapper script that you use to launch your application, and pass the information to Java via the system properties. For instance, on a GNU/Linux system the tty(1) command can be used to tell if stdin is a connected to a "tty" device.
Note that there are other ways to deal with a requirement to avoid unwanted prompts when running non-interactively:
If System.out is redirected to a file I also do not want to prompt.
(I think you might mean that System.in is redirected. That is the normal way to non-interactively run an application that normally takes interactive input from the user ...)
The alternatives include:
You could modify the program to write the prompts to (say) System.err and redirect that to a different place.
You could modify the program to have options that mean "don't prompt" or "take input from a file".
Since Java 6 there is the method java.lang.System.console():
Whether a virtual machine has a console is dependent upon the
underlying platform and also upon the manner in which the virtual
machine is invoked. If the virtual machine is started from an
interactive command line without redirecting the standard input and
output streams then its console will exist and will typically be
connected to the keyboard and display from which the virtual machine
was launched. If the virtual machine is started automatically, for
example by a background job scheduler, then it will typically not have
a console.
If this virtual machine has a console then it is represented by a
unique instance of this class which can be obtained by invoking the
java.lang.System.console() method. If no console device is available
then an invocation of that method will return null.

run interactive command line application from java

I normally use java.lang.ProcessBuilder and java.lang.Process to run external command line programs, and it works fine for run-and-done commands. For example, this would run "myProgram" with argument "myArg" in the working directory:
List<String> commandLine = new ArrayList<String>();
commandLine.add("myProgram");
commandLine.add("myArg");
ProcessBuilder builder = new ProcessBuilder(commandLine);
builder.redirectErrorStream(true);
Process process = builder.start();
However, say I wanted to run a script or program or something that had interactive input (it prompted me for more input after starting). Can I do that in Java with code similar to that above, or do I need a different approach? Or is there some library that can help me with this?
According to the documentation you should be able to redirect the input and output streams. This tells it to use the System.in/System.out from the parent process:
builder.redirectInput(ProcessBuilder.Redirect.INHERIT);
builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
If you want to write things to the processes's input:
If the source is Redirect.PIPE (the initial value), then the standard input of a subprocess can be written to using the output stream returned by Process.getOutputStream(). If the source is set to any other value, then Process.getOutputStream() will return a null output stream.
Redirecting stdin and stdout is certainly one option for simple command-line programs.
Using a "robot" class is another, if you actually need to script keystrokes (for example, in a test script):
http://www.java-tips.org/java-se-tips/java.awt/how-to-use-robot-class-in-java.html
http://download.java.net/jdk7/archive/b123/docs/api/java/awt/Robot.html
Writing a simple .bat file or shell script, that calls your Java program and uses "<" and ">" redirection operators is yet a third option.
It all depends on exactly what you're looking for :)

Creating a console in Java

When I try to use java.lang.System.console(), I get a null pointer. I can still write to out and read from in, but this only works when I run straight from my IDE. When I run the .jar file directly, nothing happens. How can I create a console like I'd see using std::cout for use in Java?
Edit:
I was hoping to just create one, rather than understand why I don't have one, since I need one for my program's operation.
Perhaps you're trying to get the console by double-clicking in the jar
Try creating a batch file that opens the console for you.
You can however create a console using Swing and redirect standard input/output there.
Source: Create Java console inside a GUI panel
How are you running the JAR file exactly? That would be the expected behavior for double-clicking its icon in Windows Explorer, as Kelly alluded to, but not for firing it up from the command line.
From the Console entry in the API (emphasis mine):
Whether a virtual machine has a console is dependent upon the underlying platform and also upon the manner in which the virtual machine is invoked. If the virtual machine is started from an interactive command line without redirecting the standard input and output streams then its console will exist and will typically be connected to the keyboard and display from which the virtual machine was launched. If the virtual machine is started automatically, for example by a background job scheduler, then it will typically not have a console.
java.lang.System.out and java.lang.System.in are the input/output streams for console access. Java won't create a "console" but allows you to interact with the IO streams provided by the operating system.
When you run it from a jar file (like clicking on it from a folder) you'll get the I/O streams of the GUI which don't display anywhere.
Try creating a batch file with a 'java -jar ' command in it. When you run the batch file, you should see the command window. I'm assuming windows here. Another way is to run cmd.exe directly with arguments that keep the window open, i.e. "cmd.exe /c".
Instead of running the jar file directly, open a console (you didn't specify an operating system, but this would be the command line on Windows and a console on *Nix, or Terminal on OS X). Then, run java -jar /path/to/your.jar.
The equivalent of std::cout in Java would be System.out as you probably already know.
EDIT: With regards to your edit, there is code out there to do this. For example, you can use Swing. There's even a StackOverflow answer with more than one working code sample.
See JConsole, which is a general console in java, used for instance by groovy. Or see groovy directly.

Is there a way to determine if Java System.in is "interactive"?

If the input is interactive, i.e. from the console, I want to print a command prompt e.g. ">"
But if it is redirected e.g. from a file, then I do not want to prompt.
System.in is an abstract InputStream which does not appear to have any method for this.
Maybe you could use instanceof if the concrete type is different?
As well, if System.out is redirected to a file I also do not want to prompt
AFAIK, there is no way to do this in pure Java, and even doing it in JNI / JNA would be complicated.
An alternative might be to use the new Console API introduced in JDK 1.6. This allows you to try to get a reader/writer for the console. If it succeeds, the result is guaranteed to be interactive ... in the sense that you mean.
A second alternative would be to do the "check for interactivity" in the wrapper script that you use to launch your application, and pass the information to Java via the system properties. For instance, on a GNU/Linux system the tty(1) command can be used to tell if stdin is a connected to a "tty" device.
Note that there are other ways to deal with a requirement to avoid unwanted prompts when running non-interactively:
If System.out is redirected to a file I also do not want to prompt.
(I think you might mean that System.in is redirected. That is the normal way to non-interactively run an application that normally takes interactive input from the user ...)
The alternatives include:
You could modify the program to write the prompts to (say) System.err and redirect that to a different place.
You could modify the program to have options that mean "don't prompt" or "take input from a file".
Since Java 6 there is the method java.lang.System.console():
Whether a virtual machine has a console is dependent upon the
underlying platform and also upon the manner in which the virtual
machine is invoked. If the virtual machine is started from an
interactive command line without redirecting the standard input and
output streams then its console will exist and will typically be
connected to the keyboard and display from which the virtual machine
was launched. If the virtual machine is started automatically, for
example by a background job scheduler, then it will typically not have
a console.
If this virtual machine has a console then it is represented by a
unique instance of this class which can be obtained by invoking the
java.lang.System.console() method. If no console device is available
then an invocation of that method will return null.

Categories

Resources