2

I'm trying to execute an external program with Java using ProcessBuilder, but it expects input from the user.

To be more specific, the program is PGSQL (Postgres SQL), when it's executed the program prompts the user for a password. The only way to bypass that is to save a file in the user home containing the passwords, I'm trying to avoid that, so I want to execute the program from Java and send the password using the process' output stream.

The code works fine when the program doesn't expect any user input, but when I delete the password file from the user home, the program hangs. I see that it's being executed, but nothing happens. If I debug it, it reaches the while and then nothing happens until I kill the process.

This is the code, any help will be greatly appreciated.

@Test
public void testSQLExecution() throws Exception {
String path = "C:/tmp";
List<String> commandList = new ArrayList<String>();
commandList.add("psql");
commandList.add("-f");
commandList.add("test.sql");
commandList.add("-h");
commandList.add(HOST);
commandList.add("-p");
commandList.add(PORT);
commandList.add("-U");
commandList.add(DATABASE);
commandList.add(SCHEMA);

ProcessBuilder processBuilder = new ProcessBuilder(commandList);
processBuilder.directory(new File(path));
processBuilder.redirectErrorStream(true);

Process p = processBuilder.start();

String line;
BufferedReader input = new BufferedReader(new InputStreamReader(p
    .getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(p
    .getOutputStream()));

out.write("password");
out.newLine();
out.flush();
out.close();

    // When this line is reached, the execution halts.
while (input.ready() && (line = input.readLine()) != null) {
    System.out.println(line);
}

if (p.waitFor() != 0) {
    Assert.fail("The process did not run succesfully.");
}

input.close();
}

Thanks a lot.

Nishant Bhardwaz
  • 924
  • 8
  • 21
Harry Potel
  • 83
  • 1
  • 11
  • see here http://stackoverflow.com/questions/2969766/process-requires-redirected-input – Mike Mar 05 '12 at 03:06

2 Answers2

0

Been a long time, but got exactly the same problem. Here is a SSCCE that should work:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Toto
{

    private static Logger logger = LoggerFactory.getLogger(Toto.class);

    static class Params 
    {
        public String getUserName() {
            return "PUT USERNAME HERE";
        }
        public String getHost() {
            return "PUT HOST HERE";
        }
        public String getDbName() {
            return "PUT DBNAME HERE";
        }
        public char[] getPassword() {
            return new char[]{'p','a','s','s','w','o','r','d'};
        }
        public String getSqlFile() {
            return "PUT SQL COMMAND FILE HERE";
        }
    }

    public static void main(String[] args)
    {
        Params params = new Params();

        try {
            final String userName   = params.getUserName();
            final String host       = params.getHost();
            final String dbName     = params.getDbName();
            final char[] pass       = params.getPassword();
            if (userName == null || host == null || dbName == null || pass == null) {
                logger.error("Missing the following info to execute the SQL command file: {} {} {} {}"  , userName  == null ? "username": ""
                    , host      == null ? "host"    : ""
                        , dbName    == null ? "database": ""
                            , pass      == null ? "password": "");
                return;
            }
            List<String> sb = new ArrayList<String>();
            sb.add("psql");
            sb.add("-h");
            sb.add(host);
            sb.add("-U");
            sb.add(userName);
            sb.add("-d");
            sb.add(dbName);
            sb.add("-f");
            sb.add(params.getSqlFile());
            //              sb.add("-W"); // force password prompt
            logger.debug("Executing the following command: {}", sb.toString());
            ProcessBuilder pb = new ProcessBuilder(sb);
            final Process p = pb.start();
            final BufferedReader stdinReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            final BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    try
                    {

                        OutputStreamWriter s = new OutputStreamWriter(p.getOutputStream());
                        s.write(pass);
                        s.write(System.getProperty("line.separator"));
                        s.flush();
                        System.out.println("Pass written");
                    }
                    catch(IOException e)
                    {
                        logger.error("Exception raised in the thread writting password to psql", e);
                    }
                }
            }).start();

            new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    try
                    {
                        String s;
                        while (( s=stdinReader.readLine()) != null) {
                            logger.debug("psql [STDOUT]: {}", s);
                        }
                    }
                    catch(IOException e)
                    {
                        logger.error("Exception raised in thread displaying stdout of psql", e);
                    }
                }
            }).start();
            new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    try
                    {
                        String s;
                        while (( s=stderrReader.readLine()) != null) {
                            logger.error("psql [STDERR]: {}", s);
                        }
                    }
                    catch(IOException e)
                    {
                        logger.error("Exception raised in thread displaying stderr of psql", e);
                    }
                }
            }).start();
            int returnVal = p.waitFor();
            logger.debug("Process ended with return val {} ", returnVal);

        }
        catch (Exception e) {
            logger.error("Exception raised while executing the results on server", e);
        }

    }

}
remi
  • 3,914
  • 1
  • 19
  • 37
0

I believe the prompt is going to STDERR, not STDOUT, so you'll have to open a stream connected to that and read there. When you try to read from STDOUT your code hangs waiting for output that will never arrive.

EDIT: I see you have redirected the error stream in the ProcessBuilder.

One other possibility is that the BufferedReader is waiting for a newline to finish reading, and the prompt does not end with a newline.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • That's right, the error output is redirected, so it's not it. The other possibility might be right, do you know how to fix that, or at least how to try to determine if that's whats happening? – Harry Potel Jul 13 '10 at 20:31
  • Read unbuffered and dump what you read to your local stdout to see what's happening. Then change the code so it sends the password as soon as it sees the prompt, without waiting for a newline. – Jim Garrison Jul 13 '10 at 20:34
  • I've tried that in many ways, none seem to be working. I've also tried to send the keystrokes using the Robot class but that doesn't work either. When I debug the test it gets stuck before even reading the first character from the input stream, I don't know why that happens. – Harry Potel Jul 15 '10 at 22:58
  • Well, then the only possibility is that the program is producing NO output but also not terminating. That would keep the stream open and block as you have described. If you're on Windows you should be able to use one of the tools from Sysinternals.com (ProcessMonitor, I believe) to trace all I/O and see what's happening. – Jim Garrison Jul 19 '10 at 14:49
  • Did you ever manage to fix this? This [question](http://stackoverflow.com/questions/3774909/java-runtime-exec-programm-and-pass-character-to-getch) seems similar, but no answer there also. – Wim Deblauwe Apr 07 '11 at 10:32