2

I am trying to run commands at unix server by connecting through jSch0.1.49 library. I have gone through the samples provided by jSch and even http://sourceforge.net/apps/mediawiki/jsch/index.php?title=Official_examples

I am able to read the response from the server and printed it to console but the loop is *never endin*g. I doubt why the Channele is not closing once it is finished reading response from server.

while (true) {
    while (inputStream.available() > 0) {
        int i = inputStream.read(buffer, 0, 1024);
        if (i < 0) {
            break;
        }
        System.out.print(new String(buffer, 0, i));//It is printing the response to console
    }
    System.out.println("done");// It is printing continuously infinite times

    if (channel.isClosed()) {//It is never closed 
        System.out.println("exit-status: " + channel.getExitStatus());
        break;
    }
    try{Thread.sleep(1000);}catch(Exception ee){}
}
Ebbe M. Pedersen
  • 7,250
  • 3
  • 27
  • 47
Raju Penumatsa
  • 403
  • 2
  • 5
  • 12

6 Answers6

4

The channel does not close itself when there is no input left. Try closing it yourself after you have read all data.

while (true) {
    while (inputStream.available() > 0) {
        int i = inputStream.read(buffer, 0, 1024);
        if (i < 0) {
            break;
        }
        System.out.print(new String(buffer, 0, i));//It is printing the response to console
    }
    System.out.println("done");

    channel.close();  // this closes the jsch channel

    if (channel.isClosed()) {
        System.out.println("exit-status: " + channel.getExitStatus());
        break;
    }
    try{Thread.sleep(1000);}catch(Exception ee){}
}

The only time you are going to use a loop that doesnt manually close the channel is when you have interactive keyboard input from the user. Then when the user does an 'exit' that will change the channel's 'getExitStatus'. If your loop is while(channel.getExitStatus() == -1) then the loop will exit when the user has exited. You still need to disconnect the channel and session yourself after you detect an Exit Status.

It is not listed on their example page, but JSCH hosts an interactive keyboard demo on their site. http://www.jcraft.com/jsch/examples/UserAuthKI.java

Even their demo, which I used to connect to an AIX system without changing any of their code... does not close when you exit the shell!

I had to add the following code to get it to exit properly after I had typed 'exit' in my remote session:

     channel.connect();

     // My added code begins here
     while (channel.getExitStatus() == -1){
        try{Thread.sleep(1000);}catch(Exception e){System.out.println(e);}
     }

     channel.disconnect();
     session.disconnect();
     // My Added code ends here

   }
   catch(Exception e){
     System.out.println(e);
   }
}
Damienknight
  • 1,876
  • 2
  • 18
  • 34
  • 1
    Thanks for your explanation. It cleared my doubt. So In my case, I would like to keep session open and run multiple commands based on some user activity from UI part. I will try to do this and get back to you if I face any more issues. – Raju Penumatsa May 03 '13 at 10:22
  • I'm using version 0.1.53 and there is no public method named 'close' (there is such a protected method). From my experience, Jsch will usually, but not always, close the channel when the command has completed successfully, but I cannot figure out in the code where this happens on success. It seems the consensus is to check for closed channel or closed output or exitStatus != 1 to know if the command is complete. Wish JCraft would address this question/issue. – splashout Aug 04 '16 at 18:52
  • 1
    After further testing, I've determined that my issue was related to the output not being read until after channel.isClosed() returned true. If there was too much output from the command, the channel would never be closed so the process would hang (running on Linux btw). So, at least in a non-interactive process, I can confirm that Jsch will close the channel when the command is complete as long as you read the output in a timely manner. I believe this is a similar issue as mentioned in the API for java.lang.Process: "failure to promptly ... read the output stream ... may cause [it] to block. – splashout Aug 04 '16 at 23:32
3

I think better than polling for channel exit status would be to wait for the end of the Channel.thread

Thread t = channel.getThread();
  if(t != null)
  {
      synchronized(t){
          t.wait();              
      }
      System.out.println("Channel thread completed, disconnecting session");
      session.disconnect();
  }

I've added a getThread() member to Channel that simply returns the current channel thread, also I've modified Channel.disconnect() such that the thread member is not set to zero if the thread is still alive

if(thread != null)
  {
      if(!thread.isAlive())
          thread = null;      
  }  

instead of

thread = null;

in Channel.disconnect()

Kemlath
  • 31
  • 1
2

I was also trying to remote execute multiple commands using jSch and obtain the command output to the console (standard output System.out). Also wanted to wait till all commands are executed completely on the remote machine.

After fiddling a lot and googling for hours, I was able to come up with the following:

import java.io.InputStream;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class SSHUtility
{   
    public static void main(String[] args) 
    {   
        //Separate multiple commands by semicolon
        //Do not separate commands with `exit` command, otherwise
        //commands following `exit` will not execute
        String commands = "source ~/Downloads/helloworld.sh;echo Mahesha999";  
        executeCommand("username", "password", "hostname", 22, commands);
        System.out.println("done");
    }

    public static void executeCommand(String pUser, String pPassword, String pServer, int pPort, String pCommand)
    {
        System.out.println("Executing on ssh");
        JSch lJSCH;
        Session lSession;

        lJSCH = new JSch();
        try
        {
            lSession = lJSCH.getSession(pUser, pServer, pPort);
            lSession.setConfig("StrictHostKeyChecking", "no");
            lSession.setPassword(pPassword);
            System.out.println("Connecting session...");
            lSession.connect();
            System.out.println("Session connected.");

            ChannelExec lChannelExec = (ChannelExec)lSession.openChannel("exec");
            lChannelExec.setCommand(pCommand);

            ((ChannelExec)lChannelExec).setErrStream(System.err);
            InputStream ins=lChannelExec.getInputStream();

            System.out.println("Connecting exec channel...");
            lChannelExec.connect();
            System.out.println("exec channel connected.");      
            byte[] tmp=new byte[1024];
            System.out.println("Trying to read remote command output...");
            while(true)
            {
                while(ins.available()>0)
                {
                    int i=ins.read(tmp, 0, 1024);
                    if(i<0)break;
                    System.out.print(new String(tmp, 0, i));
                }
                if(lChannelExec.isClosed())
                {
                    if(ins.available()>0) continue;
                    System.out.println("exit-status: "+lChannelExec.getExitStatus());
                    break;
                }
                try{Thread.sleep(1000);}catch(Exception ee){}
            }
            lChannelExec.disconnect();
            lSession.disconnect();
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
    }
}

/*
      Output:
Executing on ssh
Connecting session...
Session connected.
Connecting exec channel...
exec channel connected.
Trying to read remote command output...
Hello
Mahesha999
exit-status: 0
done
*/
Mahesha999
  • 22,693
  • 29
  • 116
  • 189
1

I found that the solution given above would hang. My solution removes the "while(true)", opting for a more straight forward approach.

    private void sendCommand(Channel channel, String command) {
    try {
        //
        this.channelExec = (ChannelExec) channel;
        this.channelExec.setCommand(command);
        //channel.setInputStream(null);
        channel.setOutputStream(System.out);
        this.is = channel.getInputStream();
        channel.connect();
        byte[] buffer = new byte[1024];
        while (channel.getExitStatus() == -1) {
            while (is.available() > 0) {
                int i = is.read(buffer, 0, 1024);
               // System.out.println("i= " + i);
                if (i < 0) {
                   // System.out.println("breaking");
                    break;
                }
                String string = new String(buffer, 0, i);                    
                output = output.concat(string);
                //System.out.println("String= " + string);

            }

            if (channel.isClosed()) {
                //System.out.println("exit-status: " + channel.getExitStatus());
                break;
            }

        }
        is.close();            
        channel.disconnect();
        this.session.disconnect();
        System.out.println("Done");

    } catch (IOException ex) {
        System.out.println("ERROR: " + ex);
        Logger.getLogger(SSH.class.getName()).log(Level.SEVERE, null, ex);
    } catch (JSchException ex) {
        System.out.println("ERROR: " + ex);
        Logger.getLogger(SSH.class.getName()).log(Level.SEVERE, null, ex);
    }

}
CoupFlu
  • 311
  • 4
  • 20
1

Provided your command is properly formatted, I have found Jsch will close the channel once the command is complete as long as you read the output in a timely manner. In the JCraft example, channel.isClosed() is the only thing they check for the command being complete. They read the output in the same thread that waits for the channel to close. A fancier way to do this is to create a separate thread to read the output. Example follows:

Jsch Code:

    Channel channel = null;
    int exitStatus = 0;
    List<String> lines = new ArrayList<String>();
    try {
        channel = session.openChannel("exec");
        ((ChannelExec) channel).setCommand(command);

        // Read output in separate thread
        Thread stdoutReader = new InputStreamHandler(channel.getInputStream(), "STDOUT", lines);

        // Run the command
        ((ChannelExec)channel).setCommand(command);
        channel.connect();

        // Start thread that reads output
        stdoutReader.start();

        // Poll for closed status
        boolean channelClosed = channel.isClosed();
        exitStatus = channel.getExitStatus();
        boolean stdOutReaderAlive = stdoutReader.isAlive();
        int loopCounter = 0;
        while (!channelClosed) {
            if (loopCounter % 60 == 0) {
                log.info("SSH command '" + command + "' still in while loop checking for  after " + (loopCounter/60) + " mins.");
            }
            loopCounter++;
            Thread.sleep(1000);
            channelClosed = channel.isClosed();
            exitStatus = channel.getExitStatus();
            stdOutReaderAlive = stdoutReader.isAlive();
        }

        log.info("SSH command '" + command + "' exited while loop with values: channelClosed=" + channelClosed + ", exitStatus=" + exitStatus + ", stdOutReaderAlive=" + stdOutReaderAlive);

        // finish reading output
        stdoutReader.join();

        exitStatus = channel.getExitStatus();
        log.info("SSH command '" + command + "' final exitStatus=" + exitStatus);

        for (String line : lines) {
            log.info("SSH output: " + line);
        }
    } catch (Exception e) {
        throw new RuntimeException("Error occured processing SSH request. See nested exception for details.", e);
    } finally {                 
        // Always try to close the channel and session.  
        try {
            channel.disconnect();
        } catch(Exception e) {
            this.log.error("Error - disconnecting channel", e);
        }
        try {
            session.disconnect();
        } catch(Exception e) {
            this.log.error("Error - disconnecting session", e);
        }
    }

InputStreamHandler:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A lot of debate on how to stop a thread... going with the method given here: http://www.javaspecialists.eu/archive/Issue056.html
 */
public class InputStreamHandler extends Thread {
    protected Log logger = LogFactory.getLog(getClass());
    private InputStream is = null;
    private String type = null;
    private StringBuilder buffer;
    private List<String> lines;

    public InputStreamHandler(InputStream is, String type) {
        this.is = is;
        this.type = type;
    }

    public InputStreamHandler(InputStream is, String type, StringBuilder buffer) {
        this(is, type);
        this.buffer = buffer;
    }

    public InputStreamHandler(InputStream is, String type, List<String> lines) {
        this(is, type);
        this.lines = lines;
    }

    public void run() {
        try {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            if (buffer != null) {
                String line = null;
                while ((line = br.readLine()) != null) {
                    buffer.append(line + "\n");
                }
            } else if (lines != null) {
                String line = null;
                while ((line = br.readLine()) != null) {
                    lines.add(line);
                }
            } else {
                // just consume output
                while (br.readLine() != null)
                    ;
            }
        } catch (InterruptedIOException ioe) {
            // when exception is thrown the interrupt flag is set to false... this will set it back to true
            Thread.currentThread().interrupt();
        } catch (IOException ioe) {
            if (!isInterrupted()) {
                throw new RuntimeException("Caught IQException for "
                        + this.type + " " + this.getClass().getName() + ".",
                        ioe);
            }
        } finally {
            closeInputStream();
        }
    }

    @Override
    public void interrupt() {
        super.interrupt();
        closeInputStream();
        logger.info(this.type + " " + this.getClass().getName()
                + " thread was interrupted.");
    }

    private void closeInputStream() {
        try {
            is.close();
        } catch (Exception e) {
        }
    }
}
splashout
  • 537
  • 5
  • 11
0

The best solution that I have found to this problem after a lot of trials and errors is to add an "exit" command at the end your command / commands list:

String[] commands = { 
                "sudo yum update -y",
                "sudo yum install java-1.8.0-openjdk-devel -y",
                "exit"};
String commandsList = String.join(";", commands);
Channel channel = session.openChannel("shell");
    OutputStream ops = channel.getOutputStream();
    PrintStream ps = new PrintStream(ops, true);

    channel.connect();
    ps.println(commandsList);

    InputStream input = channel.getInputStream();
    BufferedReader reader = null;
    String line;

    reader = new BufferedReader(new InputStreamReader(input));
    while ((line = reader.readLine()) != null) {
        line = reader.readLine();
        System.out.println(line);
    }