-2

I am trying to install missing dependencies on a Linux for a program I am making. I however am failing at getting the root access required to install the missing dependencies. Here is what I have so far:

My logic is as follows:

1) Check if the dependency is installed using pacapt (npm in this case) 2) if so then get the user password using a text prompt 3) then continue further instructions like so: echo [userpass] | sudo -S ...

Right now the 'echo [userpass] | sudo -S ...' command gets printed out to the shell like so; [userpass] | sudo -S ... (where the user password is displayed in place of [userpass]), but does not execute.

And here is my code:

public class LinuxDependencyCheck extends Application{
    public static void main (String [] args){
            launch(args);
    }

    @Override
    public void start(Stage mainWindow){

            String userPass = null;
            String terminalOut = null;

            terminalOut = runBash("./LinuxScripts/pacapt -Qqe npm");

            if (terminalOut.equals("npm")){
                    userPass = getUserPass();
                    if (userPass != null){
                            System.out.println("runing");
                            runBash("echo " + userPass + " | sudo -S npm install" + 
                                            " phantomjs2");
                    }
            }

    }

    public String runBash(String runCommand){
            String result = null;
            String returnVal = null;
            try {
                    Runtime r = Runtime.getRuntime();                    

                    Process p = r.exec(runCommand);

                    BufferedReader in =
                            new BufferedReader(new InputStreamReader(p.getInputStream()));
                    String inputLine;
                    while ((inputLine = in.readLine()) != null) {
                            System.out.println(inputLine);
                            result += inputLine;
                            returnVal = inputLine;
                    }
                    in.close();

            } catch (IOException e) {
                    System.out.println(e);
            }

            return returnVal;
    }

    public String getUserPass(){
            TextInputDialog dialog = new TextInputDialog("Password");
            dialog.setTitle("Installation helper");
            dialog.setHeaderText("It looks like you are missing" + 
                            " dependecies to complete this action" + 
                            " would you like to try to install" +
                            " them now");
            dialog.setContentText("Please enter your password :");

            // Traditional way to get the response value.
            Optional<String> result = dialog.showAndWait();
            if (result.isPresent()){
                    return result.get().toString();
            }
            return result.get();
    }
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    It might be easier to just have the user run your program via `sudo`, rather than have your program prompt for the password. – chrisaycock Jan 08 '16 at 20:33
  • That is one idea, however, I feel that would make my program more complicated to use as this is intended to be a program with a GUI, and therefore a command line might not always be used to launch the program with root access. – Alfredo Sequeida Jan 08 '16 at 20:39
  • 1
    running your java program as sudo could not possibly make it more complicated as sudo is a shell command and has nothing to do with your code. clicking a button to launch your program is no different at its core than running a command via terminal – user1231232141214124 Jan 08 '16 at 20:42
  • Probably a more accepted approach is to have your program's *installer* fetch the needed dependencies. I.e., if you think your application will be downloaded via a package manager, then use that to handle dependencies. That way the logic for installation is out of your core software. – chrisaycock Jan 08 '16 at 20:45
  • I would think that to execute the program with root access would require the user to open up a terminal. Otherwise, how would the program achieve root access without the user's permission (in such a way that would not seem malicious)? – Alfredo Sequeida Jan 08 '16 at 20:48
  • @chrisaycock Thank you, as I am planning to post this on github, I was planning on just stating the dependencies as my last option. However, as a more user friendly approach, I wanted to see if this was possible. – Alfredo Sequeida Jan 08 '16 at 20:51
  • @AlfredoSequeida, if it has GUI, then maybe you should use `gksu` & `kdesu` instead? – user3707125 Jan 08 '16 at 20:53
  • You'll run into [this problem](http://stackoverflow.com/questions/31776546/why-does-runtime-execstring-work-for-some-but-not-all-commands) – that other guy Jan 08 '16 at 20:54
  • @user3707125 Oh, I didn't know that excised. That could be a good option. However, if I'm not wrong from the names that looks like those are part of kde and gtk. Therefore, there might be issues with certain systems not having those packages installed. – Alfredo Sequeida Jan 08 '16 at 20:57
  • I think I might just go with the option suggested by @chrisaycock, where I list the dependencies needed. It might save me some unnecessary headaches. – Alfredo Sequeida Jan 08 '16 at 20:58

1 Answers1

0

Your runBash() method is poorly named, as it does nothing to cause the given command to be run via bash. It is therefore also inappropriate for use with a command string such as you are specifying, which relies on the shell's pipe operator to string two separate commands together.

When you do this ...

runBash("echo " + userPass + " | sudo -S npm install" + 
                                            " phantomjs2");

... Java splits the string on whitespace, takes the first substring ("echo") as the command, and executes that command with all the other substrings strings as arguments. Needless to say, that will run without error, but also without the effect you intended.

If you really want to execute the command string via bash (as it appears you do), then in your runBash() method you could change this ...

                Process p = r.exec(runCommand);

... to this ...

                Process p = r.exec("/bin/bash", "-c", runCommand);

. That should at least get you past your first hurdle.

You also should close the Process's OutputStream (by which you could have piped data into the process), and drain the Process's error stream. In general, you need to drain the input and error streams in parallel, because if either one's buffer fills up then the process can block. Perhaps that's not a risk for this particular command, but you'll need to judge. It's also good form to waitFor() the Process; doing so may avoid Java accumulating zombie child processes.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    Of course, you don't actually need to use `echo` to pipe the password into `sudo`. You could do that directly from Java by running the `sudo` as the Process's command, and sending in the password via the process's `OutputStream`. – John Bollinger Jan 08 '16 at 21:39
  • Not when sudo open /dev/tty to prevent exactly this. You need a pseudo-tty, and that's not very easy with Java. – bmargulies Jan 10 '16 at 21:58
  • @bmargulies, he is using `sudo -S` (and I also present that), which reads from `stdin` instead of from `/dev/tty` exactly so that you *can* do this. – John Bollinger Jan 10 '16 at 22:43