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);
Related
I have been working on an application that uses JSCH to scp and transfer a file to another directory in the same server. However, I want to be able to transfer a file from one remote host to another remote host in an automated way. Below is the code I use to make the call. I have tried using other scp commands such as: scp [options] username1#source_host:directory1/filename1 username2#destination_host:directory2/filename2. It works when testing it on the server itself but when I try to incorporate it into the code below, it passes back an error.
CODE:
public static void main(String[] args){
String array [] = {"robert.txt","ops1axv#gaalpltclu00029.linux.us.ams1907.com:/tmp"};
if(array.length!=2){
System.err.println("usage: java ScpTo file1 user#remotehost:file2");
System.exit(-1);
}
FileInputStream fis=null;
try{
String lfile=array[0];
String user=array[1].substring(0, array[1].indexOf('#'));
array[1]=array[1].substring(array[1].indexOf('#')+1);
String host=array[1].substring(0, array[1].indexOf(':'));
String rfile=array[1].substring(array[1].indexOf(':')+1);
System.out.println("RFile: " + rfile );
System.out.println("Lfile: " + lfile);
JSch jsch=new JSch();
Session session=jsch.getSession(user, host, 22);
// username and password will be given via UserInfo interface.
UserInfo ui=new MyUserInfo();
session.setUserInfo(ui);
session.connect();
boolean ptimestamp = true;
// exec 'scp -t rfile' remotely
String command="scp " + (ptimestamp ? "-p" :"") +" -t "+rfile;
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand(command);
// get I/O streams for remote scp
OutputStream out=channel.getOutputStream();
InputStream in=channel.getInputStream();
channel.connect();
if(checkAck(in)!=0){
System.exit(0);
}
File _lfile = new File(lfile);
if(ptimestamp){
command="T"+(_lfile.lastModified()/1000)+" 0";
// The access time should be sent here,
// but it is not accessible with JavaAPI ;-<
command+=(" "+(_lfile.lastModified()/1000)+" 0\n");
out.write(command.getBytes()); out.flush();
if(checkAck(in)!=0){
System.exit(0);
}
}
// send "C0644 filesize filename", where filename should not include '/'
long filesize=_lfile.length();
command="C0644 "+filesize+" ";
if(lfile.lastIndexOf('/')>0){
command+=lfile.substring(lfile.lastIndexOf('/')+1);
}
else{
command+=lfile;
}
command+="\n";
out.write(command.getBytes()); out.flush();
if(checkAck(in)!=0){
System.exit(0);
}
// send a content of lfile
fis=new FileInputStream(lfile);
byte[] buf=new byte[1024];
while(true){
int len=fis.read(buf, 0, buf.length);
if(len<=0) break;
out.write(buf, 0, len); //out.flush();
}
fis.close();
fis=null;
// send '\0'
buf[0]=0; out.write(buf, 0, 1); out.flush();
if(checkAck(in)!=0){
System.exit(0);
}
out.close();
channel.disconnect();
session.disconnect();
System.exit(0);
}
catch(Exception e){
System.out.println(e);
try{if(fis!=null)fis.close();}catch(Exception ee){}
}
}
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
I'm running a test case using testng. This testcase uses Jsch to try to send a ssh command to a remote machine. The Jsch function is supposed to return output from command (including any exceptions). However, I can't view the output and the test passes, even when there's been an timeout exception!
Here's the JSch function:
public static String sshCommand (String username, String password, String host, int port, String command) {
StringBuilder outputBuffer = new StringBuilder();
try {
System.out.println("about to send command: " + command);
JSch jschSSHChannel = new JSch();
Session session=jschSSHChannel.getSession(username, host, port);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
session.connect();
Channel channel = session.openChannel("exec");
((ChannelExec)channel).setCommand(command);
InputStream commandOutput = channel.getInputStream();
channel.connect();
int readByte = commandOutput.read();
while(readByte != 0xffffffff)
{
outputBuffer.append((char)readByte);
readByte = commandOutput.read();
}
channel.disconnect();
session.disconnect();
} catch (Exception e) {
System.out.println("Exception encountered returning: " + e.toString());
return e.toString();
}
return outputBuffer.toString();
}
Here's the code that evaluates the output from the ssh function:
String output = jschClass.sshCommand("username", "password", ipAddress, 22, command);
System.out.println("Command output: " + output);
Assert.assertTrue(!output.contains("Exception"));
System.out.println("Test complete");
The problem is I never see the System.out.println("Command output: " + output); printed. It skips right over that statement and executes System.out.println("Test complete"); before exiting.
Additionally, the catch statement I put in the sshcommand function never seems to execute. An exception is displayed, but it doesn't seem to be executing the code I wrote. In fact it's almost like the exception is coming from somewhere else. Here's what I see in the program's output:
about to send command: ping -c 5 google.com
com.jcraft.jsch.JSchException: java.net.ConnectException: Connection refused: connect
Test complete
at com.jcraft.jsch.Util.createSocket(Util.java:349)
at com.jcraft.jsch.Session.connect(Session.java:215)
at com.jcraft.jsch.Session.connect(Session.java:183)
at com.cisco.ui2.openstack.jsch.JschHelper.sshCommand(JschHelper.java:28)
at com.cisco.ui2.openstack.PingPublicSite.createImageAndPingPublicSite(PingPublicSite.java:91)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[removed for space]
at com.jcraft.jsch.Util.createSocket(Util.java:343)
... 28 more
Any idea what's going on?
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 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);