Short Version: Is it possible to log the stdout and stderr on the local side of a command executed remotely via ssh in the same order as it was output on the remote host? If so, how?
Long Version:
I am attempting to log the standard and error output from a remotely exec'd SSH command (using Jsch) in the same order as it was output by the remote command. In other words, if the remote command writes "a" to stdout, then "b" to stderr, then "c" to stdout, I want the log on the client (local) side to read:
a
b
c
Below is what I have so far. It comes relatively close to what I want, but I think it will be apparent that it does not guarantee the correct output order on the client side.
public int exec(String strCommand) throws ExceptionUnableToExecCommand {
JSch jsch = new JSch();
Session session = null;
ChannelExec channel = null;
try {
session = jsch.getSession(user, host, 22);
UserInfo ui = new cyclOps.jsch.UserInfo(password);
session.setUserInfo(ui);
session.connect();
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(strCommand);
channel.setInputStream(null);
InputStream in = channel.getInputStream();
InputStream err = channel.getErrStream();
channel.connect();
/* getOutput() defined below. */
return this.getOutput(channel, in, err);
} catch (JSchException | IOException e) {
throw new ExceptionUnableToExecCommand("Unable to execute " + strCommand + " " + this.toString(), e);
} finally {
if (channel != null) channel.disconnect();
if (session != null) session.disconnect();
}
}
private int getOutput(ChannelExec channel, InputStream in, InputStream err) throws IOException {
byte[] tmp = new byte[1024];
while(true){
while(in.available() > 0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
this.sshLogger.logOutputFromSSH(new String(tmp, 0, i));
}
while(err.available() > 0){
int i=err.read(tmp, 0, 1024);
if(i<0)break;
this.sshLogger.logOutputFromSSH(new String(tmp, 0, i));
}
if(channel.isClosed()){
return channel.getExitStatus();
}
try{Thread.sleep(1000);}catch(Exception ee){}
}
}
I think I should point out that this is a modified version of Exec.java from the Jsch web site examples.
How about the following chunk of code?
channel.setCommand(command);
PipedOutputStream pos=new PipedOutputStream();
PipedInputStream pis=new PipedInputStream(pos);
channel.setOutputStream(pos);
channel.setExtOutputStream(pos);
InputStream in=pis;
channel.connect();
You are working with separate streams, so no, there is no way you can possibly guarantee to get the exact same output.
that said, your code has a number of potential problems, among them:
InputStream.available is a generally useless method and should be avoided
you are reading bytes and converting arbitrary byte arrays to chars/Strings, which could result in broken output (if you are getting multi-byte chars you could end up splitting them).
reading 2 different blocking streams with a single thread can result in deadlock
Related
I am supposed to connect to a Unix server, then go to the specific folder(which has got access restrictions) and fetch the file details from there. For the same , the code that I have written is
try{
Session session = new JSch().getSession("username", "host");
session.setPassword("password");
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand("cd a/b/node01/c.ear && ls -la");
channel.connect();
channel.run();
InputStream in = channel.getInputStream();
System.out.println(channel.isConnected());
byte[] tmp = new byte[1024];
while (true)
{
while (in.available() > 0)
{
int i = in.read(tmp, 0, 1024);
if (i < 0)
break;
System.out.print(new String(tmp, 0, i));
}
if (channel.isClosed())
{
System.out.println("exit-status: " + channel.getExitStatus());
break;
}
}
channel.disconnect();
session.disconnect();
}
catch(Exception exception){
System.out.println("Got exception "+exception);
}
I am not getting the list of files that are present in the location supplied.
The output that I am getting is
true
exit-status: 1
How do I get the desired output?
Do not use shell commands to retrieve file information. Use SFTP!
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp c = (ChannelSftp)channel;
java.util.Vector vv = c.ls("/a/b/node01/c.ear");
for (int i = 0; i < vv.size(); i++)
{
System.out.println(vv.elementAt(i).toString());
}
See http://www.jcraft.com/jsch/examples/Sftp.java.html
Regarding your code for "exec" channel:
First, the code has a recurring flaw described in:
Randomly getting empty output while executing shell command via JSch.
Though to actually debug your problem, you have to read both stdout and stderr to collect any errors (which you are obviously getting).
For that see Log stdout and stderr from ssh command in the same order it was created.
A possible problem here is the usage of in.available(). It returns the number of bytes that can be read without blocking.
The correct way to read a stream until eof is:
byte[] buf = new byte[1024];
int len;
while ((len=in.read(buf)) >= 0) {
// do something with buf[0 .. len]
}
I'm working on a project to restart something within a screen on a remote server using JSch... however I'm running into an issue. It will send the first 2 commands 100% of the time, but the 3rd command will only be sent some of the time. Any help would be appreciated.
public static void stopServer(String name, String ip, String passwd)
{
try {
Session session = Main.jsch.getSession("user",ip,22);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword(passwd);
session.connect();
Channel channel = session.openChannel("shell");
channel.connect();
ChannelShell cs = (ChannelShell) channel;
cs.setPty(true);
DataInputStream dataIn = new DataInputStream(channel.getInputStream());
PrintStream dataOut = new PrintStream(channel.getOutputStream());
dataOut.println("screen -x "+name);
dataOut.flush();
dataOut.println("stop");
dataOut.flush();
String line = dataIn.readLine();
System.out.println(line);
while(true) {
line = dataIn.readLine();
System.out.println(line);
if(line.contains("\"quit\""))
{
break;
}
}
TimeUnit.SECONDS.sleep(1);
dataOut.println("quit");
dataOut.flush();
System.out.println("Shutdown");
dataIn.close();
dataOut.close();
channel.disconnect();
session.disconnect();
}catch(Exception e)
{
e.printStackTrace();
}
}
This sends the screen -x 'name' and the stop 100% of the time from what I've seen, but the 'quit' command only actually gets sent/understood by the console a fraction of the time. Any help would be appreciated!!
I managed to fix this in a way that doesn't use the shell channel, but it is reliable enough that it works:
//Start EXEC
ChannelExec ce = (ChannelExec) session.openChannel("exec");
ce.setCommand("screen -S " + name + " -X stuff 'quit\n'");
ce.setInputStream(null);
ce.setErrStream(System.err);
InputStream in=ce.getInputStream();
ce.connect();
byte[] tmp=new byte[1024];
while(true){
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
//System.out.print(new String(tmp, 0, i));
}
if(ce.isClosed()){
if(in.available()>0) continue;
//System.out.println("exit-status: "+ce.getExitStatus());
break;
}
try{Thread.sleep(1000);}catch(Exception ee){}
}
ce.disconnect();
//STOP EXEC
As much as this doesn't work along the original vein of the problem, it fixed it for me.
If an exception gets thrown sometime before the "quit" command gets sent, then the quit command will never get sent.
If the while loop never breaks, then that will prevent the "quit"
command from being sent too.
I'm running shell script on a remote machine using JSch and printing the command logs in my log file using JSch channel. Problem is that as the script ends, I do a channel.disconnect and soon after disconnect, the System.out stop printing into the log file. Here is the code:
private int runShellScript(HashMap bundleDetails) {
int exitStatus = -1;
Channel channel = null;
Session session = null;
try {
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
String host = (String) bundleDetails.get("host");
String userName = (String) bundleDetails.get("userName");
String password = (String) bundleDetails.get("password");
String bundleName = findFileName((String) bundleDetails.get("bundleName"));
String sourceLocation = (String) bundleDetails.get("sourceLocation");
String logFileName = findFileName((String) bundleDetails.get("logFileName"));
String targetLocation = (String)bundleDetails.get("targetLocation");
String command1 = "sh "+(String) bundleDetails.get("targetIndexerLocation") + (String) bundleDetails.get("deployScript")+" "+
targetLocation + bundleName + " " +
targetLocation + logFileName;
JSch ssh = new JSch();
session = ssh.getSession(userName, host, 22);
session.setConfig(config);
session.setPassword(password);
session.connect();
channel = session.openChannel("exec");
((ChannelExec)channel).setCommand(command1);
channel.setInputStream(null);
((ChannelExec)channel).setErrStream(System.err);
InputStream in=channel.getInputStream();
channel.connect();
byte[] tmp=new byte[1024];
while(true){
//System.out.println("inside while second");
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
System.out.print("*****NEW ONE*****$$$$$**$$########"+new String(tmp, 0, i));
}
if(channel.isClosed()){
exitStatus = channel.getExitStatus();
System.out.println("Before Disconnected Here exit-status: "+exitStatus);
channel.disconnect();
System.out.println("Disconnected Here exit-status: "+exitStatus);
break;
}
}
//logger("runShellScript", "END");
System.out.println("***** out of infinite loop");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Copy to remote location failed.... ");
}finally{
System.out.println("finally DISCONNECT channel and session");
if(channel!=null && channel.isConnected()){
channel.disconnect();
}
if(session!=null){
session.disconnect();
}
System.out.println("finally DISCONNECTED channel and session");
}
System.out.println("Before return exit-status: "+exitStatus);
return exitStatus;
}
The lines from log file:
*****NEW ONE*****$$$$$**$$########Starting...
Before Disconnected Here exit-status: 0
If you see in the method I pasted above, the sysout printed is actually the one just above the 'channel.disconnect'. The one below it is not printed! Al the functioning is correct and the overall output is what I expect
System.out.println("Before Disconnected Here exit-status:
"+exitStatus); channel.disconnect(); System.out.println("Disconnected
Here exit-status: "+exitStatus);
All the functioning is correct and the overall output is what I expect. The only problem is that log freeze. Where am I going wrong?
Edit
Also, I'm not able to see the syouts from my finally block!!
Most likely it is related to this line:
((ChannelExec)channel).setErrStream(System.err);
By doing that, you've tied the System.err stream to the channel stream. And per the documentation, by default, the stream is closed when the channel is disconnected. You don't say what platform you are running on, but I think most platforms connect System.err and System.out in certain ways, so Jsch is most likely closing the System.out when it disconnects. You might try doing this to prevent JSch from closing the stream:
((ChannelExec)channel).setErrStream(System.err, true);
Javadoc
Even if that does work though, I think hooking in to the System.err like this is a bit risky. I think safer design would be to create a stream that wrote to the log file directly, not via System.err.
This is because of
((ChannelExec)channel).setErrStream(System.err);
And when you are disconnecting the channel, the connected streams are also being disconnected.
So please write the below statement before disconnecting the channel:
((ChannelExec)channel).setErrStream(null);
I am executing a rm command via ssh using the jsch library.
The command is being sent and executed correctly.
But when the file I want to delete is not found in the directory, I get an error message as follows:
No such file or directory
I want to store the error message in a variable and use it afterwards.
Currently this message is displayed when the following are executed:
((ChannelExec)channel).setErrStream(System.err);
InputStream in=channel.getInputStream();
channel.connect();
after the channel.connect() the message error mesg is displayed.
How can I store it in a variable let's say string and print it after completion?
Below is the main class.
String SSHHOST = "127.0.0.1";
int SSHPPORT = 22;
String SSHPUSER = "user";
String SSHPPASS = "pass";
StringBuilder result = new StringBuilder();
System.out.println("Connecting To Server "+SSHHOST);
JSch jsch = new JSch();
Session session=jsch.getSession(SSHPUSER, SSHHOST, 22);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword(SSHPPASS);
session.connect();
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand("find /home/test/DC* -print -exec rm {} \\;|wc -l");
channel.setInputStream(null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
((ChannelExec)channel).setErrStream(bos) ;
((ChannelExec)channel).setErrStream(System.err);
InputStream in=channel.getInputStream();
channel.connect();
String result2 = bos.toString();
System.out.println(result2);
byte[] tmp=new byte[1024];
while(true)
{
while(in.available()>0)
{
int i=in.read(tmp, 0, 1024);
if(i<0)break;
out=new String(tmp, 0, i);
}
if(channel.isClosed())
{
//System.out.println("exit-status: "+channel.getExitStatus());
break;
}
try
{
Thread.sleep(1000);
}catch(Exception ee){}
}
channel.disconnect();
session.disconnect();
int status = channel.getExitStatus();
System.out.println(out.trim());
if (channel.getExitStatus() == 0)
{
System.out.println("Perso file purging process completed successfully.");
System.out.println("exit-status: "+channel.getExitStatus());
System.out.println("Number of perso files deleted : "+out);
}
else
{
System.out.println("Perso file purging process completed with errors.");
}
I am still unable to get the error message when the files are not found in the directory with the above code.
This is a bit of a guess from me but .....
You seem to be setting the error stream of your channel object to the System.err of your application - unless you have reconfigured the System.err of your application to point to somewhere else, that will be the console.
What arguments does setErrStream take? If it's an OutputStream, could you get it to write to a ByteArrayOutputStream and then build a string from that?
i have been able to store the error in a file by using FileOutputStream
FileOutputStream fos = null;
File file = new File("errors.txt");
try
{
fos = new FileOutputStream(file);
}
catch(IOException ioe)
{
System.err.println("redirection not possible: "+ioe);
System.exit(-1);
}
PrintStream ps = new PrintStream(fos);
((ChannelExec)channel).setErrStream(ps);
I am new to JSch and I have a problem with some of my script I try to execute remotely and which seems to never end (and does not do the same thing as when I run it using putty).
I have redirected error and output stream to my System.out and see indeed error when the script is executed but the script is finished! Therefore I don't understand why the channel is still open (isClosed and isEOF are false).
When I run the command when connecting in SSH with putty the script execute correctly and does not show any errors. When I do ssh user#host "my command" using ssh command in Ubuntu I get the same output (std + err) as when I use JSch but the ssh command does not hangs!
Do you have any idea of what I do wrong, why does I have different output/behavior? Here is the java code I run (by the way I CAN'T send several command with different channel on the same sessions and I have no idea why, I therefore open one session for each cmd).
public static void runCommand(String user, String password, String cmd) throws JSchException, IOException{
Session session = jSsh.getSession(user, SERVER, SSH_PORT);
session.setPassword(password);
session.setConfig(SSH_PROPERTIES);
session.connect();
SshCommand sshCmd = new SshCommand(session, cmd);
runCommand(sshCmd);
session.disconnect();
}
private static void runCommand(SshCommand sshCmd) throws IOException, JSchException{
Session session = sshCmd.getSshSession();
String cmd = sshCmd.getCmd();
UtilityLogger.log(Level.FINE, "Running command on ssh : "+cmd);
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(cmd);
channel.setInputStream(null);
InputStream in = channel.getInputStream();
InputStream err = channel.getErrStream();
UtilityLogger.log(Level.FINEST, "Connecting to channel");
channel.connect();
UtilityLogger.log(Level.FINEST, "Channel connected");
byte[] tmp = new byte[1024];
byte[] tmp2 = new byte[1024];
while (true) {
//Flush channel
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0)
break;
UtilityLogger.log(Level.FINE, new String(tmp, 0, i));
}
//Flush Error stream
while (err.available() > 0) {
int i = err.read(tmp2, 0, 1024);
if (i < 0)
break;
UtilityLogger.log(Level.FINE, new String(tmp2, 0, i));
}
if(DONT_WAIT_PROCESS_END)
break;
if (channel.isEOF()) {
UtilityLogger.log(Level.FINE, "Channel exit-status: " + channel.getExitStatus());
break;
}
}
try{Thread.sleep(TIME_BETWEEN_COMMAND);}catch(Exception ee){}
channel.disconnect();
UtilityLogger.log(Level.FINEST, "Channel disconnected");
}
Try appending "exit;" after your commands even while using exec channels.
Our app did not receive an EOF on the exec, too. Appending an exit; to the command did not solve the problem.
It had something to do with the stderr output. Redirecting stderr to stdout solved (workarounded?!) the problem.
So we appended 2>&1 to the command:
${command} 2>&1