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();
}
}
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.
Related
public class Controller {
public String printResults(Process process) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
String container = "";
while ((line = reader.readLine()) != null) {
container = container + line + "\n";
System.out.println(line);
}
return container;
}
public String executeCmd(String firstname, String lastname) throws IOException {
String command = "./myprogram -n " + firstname + " -s " + lastname;
Process p = Runtime.getRuntime().exec(command, null, new File("/home/user/myprogram/build/"));
return "Hi, \n" + printResults(p);
}
}
This allowes me to run a single command with attributes from a specific directory and get back result.
However, I have a larger program, which asks user for inputs from terminal.
user#debian:~/program/build/$./program
...
Enter Value: 5
...
Enter Name: Hanz
...
Enter State: GE
Output..
How can I run that program and enter user input from Java?
First, rewrite your code to use ProcessBuilder; it's pretty much just replacing exec with new ProcessBuilder.
That's because PB gives you some flexibility that may well come up. For example, you can redirect inputs and outputs to files, for example.
Then, run start() on the builder and this gets you a Process object. You can use this to get the OutputStream, and then you can write your string to that: p.getOutputStream().write("5\nHanz\GE".getBytes());
There are a couple of different ways to do the job; Have a look at the APIs of Process and ProcessBuilder, there's lots of interesting stuff in there :)
I tried to piping password to smbpassword via Java application, here how I pipe via terminal:
(echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
and the output show the command is nicely done:
Added user client1
However, I cannot accomplish this via Java application, here the codes that I use:
public void run(String command, String[] prompt) {
try {
String[] args = new String[] {"/bin/bash","-c","echo " + rootPassword + "| sudo -S " + command};
Process proc = new ProcessBuilder(args).start();
if (prompt != null && prompt.length > 0) {
for (int i = 0; i < prompt.length; i++) {
proc.getOutputStream().write((prompt[i] + "\r\n").getBytes());
proc.getOutputStream().flush();
}
}
proc.waitFor();
String output = "";
String line;
BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
output += line + "\n";
}
System.out.println(output);
input.close();
}
catch (Exception ex) {
System.out.println(null, ex.getMessage());
}
}
and I tried to use OutputStream it like:
String[] pipe = { password, password };
run("smbpasswd -s -a " + username, pipe);
or pipe it like:
run("(echo " + password + "; echo " + password + ") | smbpasswd -s -a " + username, null);
but both doesn't work and I got no output and the user seems not created after I check via pdbedit -L. However, I able to execute another command with pipe such as echo username:password | chpasswd via that function.
Any idea?
Thanks in advance.
The main conceptual problem is that shell code such as you are trying to run:
(echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
is not a "command" in the sense that sudo requires. In fact, it's not a "command" even in shell terminology; rather, it's a pipeline, consisting of a compound command and a simple command. sudo expects you to provide a simple command.
But that's actually a bit of an aside. Your problem isn't really specific to sudo; rather, it's a shell problem. In the failure case, your run() method invokes bash to execute the following shell code:
echo rootPassword | sudo -S (echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
That's problematic for at least two reasons:
A subshell invocation cannot appear in a simple command's (i.e. sudo's) argument list like that. You could put a command substitution there ($(echo ...)), but that will not serve your purpose because the internal line break will not then be preserved.
Even if you solved (1), your second pipe is at the wrong level: it would pipe the output of sudo into smbpasswd. That's fine data-wise, but it leaves smbpasswd running without privilege -- the sudo does not apply to it.
You could probably solve the problem by instructing sudo to run the code via a shell. You could do that by embedding the ultimate command inside an inner bash -c command, but it would probably be easier to use sudo's -s option (but beware that -s can choose a different shell than bash if you let it do). Something along those lines is probably the most general solution if you want to encode the whole operation in a single String in your Java source:
String[] args = new String[] { "/bin/bash", "-c",
"echo " + rootPassword + " | sudo -S -s '" + command + "'" };
String[] args = new String[] { "/bin/bash", "-c",
"echo " + rootPassword + " | sudo -S bash -c '" + command + "'" };
Much of your problem, however, arises from your reliance on echo to feed data to your commands. You should consider instead using your Process's OutputStream to feed data to it from your Java program. That does not lend itself to quite as simple a Java-side API, but I think you're fooling yourself a bit about how simple your API really is. It has hidden gotchas, as you discovered, and even the revision I offered above still has at least one: it will likely break if the command submitted to it contains a single-quote character (').
Update:
Here's one way to feed the process the needed data via its OutputStream:
public void run2(String command, String commandInput)
throws IOException, InterruptedException {
String[] args = new String[] { "sudo", "-S", "bash", "-c", command };
Process proc = new ProcessBuilder(args).start();
Writer toProc = new OutputStreamWriter(proc.getOutputStream());
toProc.write(rootPassword, 0, rootPassword.length());
toProc.write('\n');
toProc.write(commandInput, 0, commandInput.length());
toProc.close();
proc.waitFor();
BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = input.readLine()) != null) {
output.append(line).append('\n');
}
System.out.println("Command output:");
System.out.println(output.toString());
input.close();
}
You might use that from within its class as
run2("smbpasswd -a -s client1", "newPassword\nnewPassword");
Note in particular that the Process's output stream must, in general, be closed after you've written everything you intend to write to it. Some commands you could run will not exit if you do not do so.
Note also
For the general case, you must read every Process's input stream and error stream (or just the former if you combine them), in parallel with the process itself. If you have not combined the streams then you must read them in parallel with each other. If you are writing to the process's output stream then that, too, must be in parallel. If you do not arrange to handle each stream via its own thread then it is possible for the process to get stuck.
On the other hand, you don't need a separate thread to run the Process itself in -- after all, it represents an entire separate process. Just defer waiting for it until you have closed its output stream and read the end of its input and error streams.
I have a VB script to which I need to pass username and password.
I want to run this VB script through Java code programmatically.
Is there a way that I can pass the Windows credentials to the VB script in Java programmatically?
You can have the credentials on the OS environment and read them from there:
String credentials = System.getenv().get("encrypted_credentials_or_something");
And then run your command from Java. However, Runtime.exec() won't work in some cases:
When the command is not on the System's PATH
When arguments are involved
When you want to have access to the process output
When you need to be able to kill the process
When you need to check if it terminated successfully or in error (status code != 0 - which is why you write System.exit(int) to terminate a Java application. The System.exit(1), for example, indicates abnormal termination)
That's why I created this utility class to execute external processes with arguments and everything. It works very well for me:
import java.io.*;
import java.util.*;
public class ExternalCommandHelper {
public static final void executeProcess(File directory, String command) throws Exception {
InputStreamReader in = null;
try {
//creates a ProcessBuilder with the command and its arguments
ProcessBuilder builder = new ProcessBuilder(extractCommandWithArguments(command));
//errors will be printed to the standard output
builder.redirectErrorStream(true);
//directory from where the command will be executed
builder.directory(directory);
//starts the process
Process pid = builder.start();
//gets the process output so you can print it if you want
in = new InputStreamReader(pid.getInputStream());
//simply prints the output while the process is being executed
BufferedReader reader = new BufferedReader(in);
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int status = 0;
//waits for the process to finish. Expects status 0 no error. Throws exception if the status code is anything but 0.
if ((status = pid.waitFor()) != 0) {
throw new IllegalStateException("Error executing " + command + " in " + directory.getAbsolutePath() + ". Error code: " + status);
}
} finally {
if (in != null) {
in.close();
}
}
}
//Splits the command and arguments. A bit more reliable than using String.split()
private static String[] extractCommandWithArguments(String command) {
StringTokenizer st = new StringTokenizer(command);
String[] cmdWithArgs = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++) {
cmdWithArgs[i] = st.nextToken();
}
return cmdWithArgs;
}
}
All,
I originally had a shell script that called SQLLoader (Oracles data upload tool).
The problem was that SQLLoader takes a plain text password as input so I decided to build a Java application to call SQLLoader internally passing a decrypted password into the command string.
e.g.
sqlldr user/pass#DBServer control=../sqlloader.ctl log=sqlloader.log data=mydata.csv
So with my java wrapper it became this in my shell script
java -jar sqlloader.jar sqlloader.ctl mydata.csv
However a new problem developed when SQLLoader complained there was no file to load. After some head scratching it was discovered that a subsequent command in my shell script seemed to be executing while my java application was still running. Therefore it was behaving asynchronously.
The next command was moving the input file sqlloader was using before it could get a chance to use it. So I put a sleep command in of 20 seconds to give my java application time to run.
java -jar sqlloader.jar sqlloader.ctl mydata.csv
echo $?
sleep 20
if [ $? -ne 0 ]
then
echo "SQLLoader failed during execution, please check the log : "
mv mydata.csv
else
echo "SQLLoader successfully processed file : "
mv mydata.csv
fi
Does anyone know why unix is behaving this way, does Java execute my SQLLoader as a different user/ thread?
This is my java code:
Runtime Rt;
Process Prc;
Prc = Rt.exec("sqlldr user/decryptedpass#DBServer control=../sqlloader.ctl log=sqlloader.log data=mydata.csv);
system.exit(0);
I checked the Runtime Class for anything about it being Asynchronous but couldnt find anything
http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html
Any theories or suggestions?
Thanks
Yes. If you look at Runtime.exec again it does specify that it will launch a new process in the specified environment (e.g. independently of the current "environment" or as you put it asynchronously). You should use ProcessBuilder to create a Process and then waitFor that Process to finish before calling System.exit - which certainly isn't mandatory. Something like this
public static void main(String[] args) {
// String command = "/usr/bin/sleep 5";
List<String> command = new ArrayList<String>();
command.add("c:/cygwin/bin/sleep");
command.add("5");
ProcessBuilder pb = new ProcessBuilder(command);
BufferedReader is = null;
try {
System.out.println("Starting command " + command);
Process p = pb.start();
int ret = p.waitFor();
is = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
System.out.println(line);
}
if (ret == 0) {
System.out.println("Command has completed.");
System.exit(ret);
} else {
System.out.println("Command completed with return code " + ret);
System.exit(ret);
}
} catch (Exception e) {
System.out.println("Caught Exception " + e.getMessage()
+ " running command " + command);
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
System.out.println("COMMAND FAILED");
System.exit(1);
}
You need to wait for process completion, you should also read all output (stdout and stderr) from the process you are starting.
If you call exit() after exec(), Java will do just that - exit immediatedly.
Here is an article that explains Runtime.exec pitfalls: http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4 (also consider the other pages).
I'm aware how to open an exe program with parameters in Java from finding the answer online. However my specific shortcut is a bit complicated for me to understand.
I'm trying to open a shortcut which has the following target:
C:\Windows\System32\javaw.exe -cp jts.jar;total.2012.jar -Dsun.java2d.noddraw=true -Dswing.boldMetal=false -Dsun.locale.formatasdefault=true -Xmx768M -XX:MaxPermSize=128M jclient/LoginFrame C:\Jts
In my program I've split up the location and what I think are the parameters. However when I run the program I get the error 'Could not create Java Virtual Machine, Program will Exit'. Can someone with a better understanding of whats going on explain what I might be doing wrong or point me in a direction where I can read up?
String location = "C:\\Windows\\System32\\javaw.exe";
String p1="-cp jts.jar;total.2012.jar";
String p2="-Dsun.java2d.noddraw=true";
String p3="-Dswing.boldMetal=false";
String p4="-Dsun.locale.formatasdefault=true";
String p5="-Xmx768M";
String p6="-XX:MaxPermSize=128M";
String p7="jclient/LoginFrame" ;
String p8 = "C:\\Jts";
try {
Process p = new ProcessBuilder(location,p1,p2,p3,p4,p5,p6,p7,p8).start();
} catch (IOException ex) {
Logger.getLogger(Openprogramtest.class.getName()).log(Level.SEVERE, null, ex);
}
Each String you pass to ProcessBuilder is a separate argument (except the first one, which is the command).
Think of it like the args[] which are passed to your main method. Each String would be a separate element in the array.
I suspect that p1 is been interpreted as a single argument, when it should actually be two...
Try separating this argument into two separate parameters
String location = "C:\\Windows\\System32\\javaw.exe";
String p1="-cp";
String p2="jts.jar;total.2012.jar";
String p3="-Dsun.java2d.noddraw=true";
String p4="-Dswing.boldMetal=false";
String p5="-Dsun.locale.formatasdefault=true";
String p6="-Xmx768M";
String p7="-XX:MaxPermSize=128M";
String p8="jclient/LoginFrame" ;
String p9 = "C:\\Jts";
Amendment
Look at the -cp parameter, it appears that the class path elements are relative to the location that the command is executed. This suggests that you need to use the ProcessBuilder#directory(File) to specify the location that the command should executed from.
For example, if you program is installed in C:\Program Files\MyAwesomeApp, but you run it from the context of C:\Desktop, then Java won't be able to find the Jar files it needs, generally raising a ClassNotFound exception.
Instead, you need to tell ProcessBuilder that you want the command to executed from within the C:\Program Files\MyAwesomeApp context.
For example...
ProcessBuilder pb = new ProcessBuilder(...);
pb.directory(new File("C:\Program Files\MyAwesomeApp"));
// Other settings...
Process p = pb.start();
Updated from running example
Just to make the point. I built myself a little Java program that simple printed a simple message to the standard out.
When I run this, it works as expected...
try {
String params[] = new String[]{
"C:\\Windows\\System32\\javaw.exe",
"-cp",
"C:\\...\\TestSimpleProcessBuilder\\build\\classes",
"-Dsun.java2d.noddraw=true",
"-Dswing.boldMetal=false",
"-Dsun.locale.formatasdefault=true",
"-Xmx768M",
"-XX:MaxPermSize=128M",
"testsimpleprocessbuilder/HelloWorld",
"Boo"
};
ProcessBuilder pb = new ProcessBuilder(params);
pb.redirectErrorStream();
Process p = pb.start();
InputStream is = p.getInputStream();
int in = -1;
while ((in = is.read()) != -1) {
System.out.print((char) in);
}
is = p.getErrorStream();
in = -1;
while ((in = is.read()) != -1) {
System.out.print((char) in);
}
System.out.println("p exited with " + p.exitValue());
} catch (IOException ex) {
Logger.getLogger(TestSimpleProcessBuilder.class.getName()).log(Level.SEVERE, null, ex);
}
When I change the arguments from
"-cp",
"C:\\...\\TestSimpleProcessBuilder\\build\\classes",
to
"-cp C:\\...\\TestSimpleProcessBuilder\\build\\classes",
It fails with...
And outputs
Unrecognized option: -cp
C:\DevWork\personal\java\projects\wip\StackOverflow\TestSimpleProcessBuilder\build\classes
And if you're wondering, this is the little test program I wrote that gets run...
package testsimpleprocessbuilder;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world - world says " + (args.length > 0 ? args[0] : "Nothing"));
}
}