1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-03 15:15:25 +02:00

Fix ssh shell session timeouts by using a separate Thread.

Improve ssh terminal handling (backspace)
This commit is contained in:
Martin Oberhuber 2006-08-08 14:14:31 +00:00
parent f2e6f47b3f
commit a219176dd1
3 changed files with 190 additions and 35 deletions

View file

@ -18,25 +18,29 @@ package org.eclipse.rse.services.ssh.shell;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import org.eclipse.core.runtime.IProgressMonitor;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.Session;
import org.eclipse.rse.services.shells.AbstractHostShell;
import org.eclipse.rse.services.shells.IHostShell;
import org.eclipse.rse.services.shells.IHostShellOutputReader;
import org.eclipse.rse.services.ssh.ISshSessionProvider;
/**
* A Shell subsystem for SSH.
*/
public class SshHostShell extends AbstractHostShell {
public class SshHostShell extends AbstractHostShell implements IHostShell {
private ISshSessionProvider fSessionProvider;
private Channel fChannel;
private SshShellOutputReader fStdoutHandler;
private SshShellOutputReader fStderrHandler;
private PrintWriter fStdinHandler;
private SshShellWriterThread fShellWriter;
public SshHostShell(ISshSessionProvider sessionProvider, String initialWorkingDirectory, String commandToRun, String encoding, String[] environment) {
try {
@ -53,21 +57,43 @@ public class SshHostShell extends AbstractHostShell {
fStdoutHandler = new SshShellOutputReader(this, new BufferedReader(new InputStreamReader(fChannel.getInputStream())), false);
fStderrHandler = new SshShellOutputReader(this, null,true);
fStdinHandler = new PrintWriter(fChannel.getOutputStream());
OutputStream outputStream = fChannel.getOutputStream();
//TODO check if encoding or command to execute needs to be considered
//If a command is given, it might be possible to do without a Thread
//Charset cs = Charset.forName(encoding);
//PrintWriter outputWriter = new PrintWriter(
// new BufferedWriter(new OutputStreamWriter(outputStream,cs)));
PrintWriter outputWriter = new PrintWriter(outputStream);
fShellWriter = new SshShellWriterThread(outputWriter);
fChannel.connect();
writeToShell("cd "+initialWorkingDirectory); //$NON-NLS-1$
if (initialWorkingDirectory!=null && initialWorkingDirectory.length()>0 && !initialWorkingDirectory.equals(".")) { //$NON-NLS-1$
writeToShell("cd "+initialWorkingDirectory); //$NON-NLS-1$
}
} catch(Exception e) {
//TODO Forward exception to RSE properly
e.printStackTrace();
if (fShellWriter!=null) {
fShellWriter.stopThread();
fShellWriter = null;
}
}
}
/**
* Connect to remote system and launch Threads for the
* shell as needed.
* @param monitor
*/
protected void start(IProgressMonitor monitor)
{
}
public boolean isActive() {
if (fChannel!=null && !fChannel.isEOF()) {
return true;
}
// shell is not active: check for session lost
exit();
Session session = fSessionProvider.getSession();
if (session!=null && !session.isConnected()) {
fSessionProvider.handleSessionLost();
@ -77,8 +103,11 @@ public class SshHostShell extends AbstractHostShell {
public void writeToShell(String command) {
if (isActive()) {
fStdinHandler.println(command);
fStdinHandler.flush();
if (!fShellWriter.sendCommand(command)) {
//exception occurred: terminate writer thread, cancel connection
exit();
isActive();
}
}
}
@ -91,6 +120,7 @@ public class SshHostShell extends AbstractHostShell {
}
public void exit() {
fShellWriter.stopThread();
fChannel.disconnect();
}

View file

@ -37,11 +37,12 @@ public class SshShellOutputReader extends AbstractHostShellOutputReader
implements IHostShellOutputReader {
protected BufferedReader fReader;
private String fPromptChars = ">$%#]"; //Characters we accept as the end of a prompt //$NON-NLS-1$;
public SshShellOutputReader(IHostShell hostShell, BufferedReader reader,
boolean isErrorReader) {
super(hostShell, isErrorReader);
setName("SshShellOutputReader-"+getName()); //$NON-NLS-1$
setName("Ssh ShellOutputReader"+getName()); //$NON-NLS-1$
fReader = reader;
}
@ -58,73 +59,83 @@ public class SshShellOutputReader extends AbstractHostShellOutputReader
return null;
}
StringBuffer theLine = new StringBuffer();
StringBuffer theDebugLine = null;
theDebugLine = new StringBuffer();
int ch;
int lastch = 0;
boolean done = false;
while (!done && !isFinished()) {
try {
ch = fReader.read();
switch (ch) {
case -1:
case 65535:
if (theLine.length() == 0) // End of Reader
return null;
done = true;
break;
case 65535:
if (theLine.length() == 0) // Check why I keep getting this!!!
return null;
done = true;
break;
case '\b': //backspace
if(theDebugLine!=null) theDebugLine.append((char)ch);
int len = theLine.length()-1;
if (len>=0) theLine.deleteCharAt(len);
case 13:
if(theDebugLine!=null) theDebugLine.append((char)ch);
break; // Carriage Return: dont append to the buffer
case 10:
if(theDebugLine!=null) theDebugLine.append((char)ch);
done = true; // Newline
break;
case 9:
//TODO Count characters and insert as many as needed to do a real tab
theLine.append(" "); // Tab //$NON-NLS-1$
//Tab: we count tabs at column 8
if(theDebugLine!=null) theDebugLine.append((char)ch);
int tabIndex = theLine.length() % 8;
while (tabIndex < 8) {
theLine.append(' ');
tabIndex++;
}
break;
case 13:
break; // Carriage Return
default:
char tch = (char) ch;
if(theDebugLine!=null) theDebugLine.append(tch);
if (!Character.isISOControl(tch)) {
theLine.append(tch); // Any other character
} else if (ch == 27) {
// Escape: ignore next char too
int nch = (char)fReader.read();
int nch = fReader.read();
if (theDebugLine!=null) theDebugLine.append((char)nch);
if (nch == 91) {
//vt100 escape sequence: read until end-of-command (skip digits and semicolon)
//e.g. \x1b;13;m --> ignore the entire command, including the trailing m
do {
nch = fReader.read();
if (theDebugLine!=null) theDebugLine.append((char)nch);
} while (Character.isDigit((char)nch) || nch == ';');
}
}
}
boolean ready = fReader.ready();
//TODO Get rid of this to support UNIX and Mac? -- It appears that
//due to vt100 emulation we get CRLF even for UNIX connections.
if (ch == 10 && lastch == 13) {
return theLine.toString();
}
lastch = ch;
// Check to see if the BufferedReader is still ready which means
// there are more characters
// in the Buffer...If not, then we assume it is waiting for
// input.
if (!ready) {
// wait to make sure
if (!done && !fReader.ready()) {
// wait to make sure -- max. 500 msec to wait for new chars
// if we are not at a CRLF seems to be appropriate for the
// Pipes and Threads in ssh.
long waitIncrement = 500;
// Check if we think we are at a prompt
int len = theLine.length()-1;
while (len>0 && Character.isSpaceChar(theLine.charAt(len))) {
len--;
}
if (len>=0 && fPromptChars.indexOf(theLine.charAt(len))>=0) {
waitIncrement = 5; //wait only 5 msec if we think it's a prompt
}
try {
Thread.sleep(_waitIncrement);
Thread.sleep(waitIncrement);
} catch (InterruptedException e) {
}
if (!fReader.ready()) {
if (done) {
return theLine.toString().trim();
} else {
done = true;
}
done = true;
}
}
} catch (IOException e) {
@ -132,9 +143,14 @@ public class SshShellOutputReader extends AbstractHostShellOutputReader
//our reader thread completely... the exception could just be
//temporary, and we should keep running!
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.getDefault().getBundle().getSymbolicName(), 0, "IOException in SshShellOutputReader", e));
//e.printStackTrace();
return null;
}
}
if (theDebugLine!=null) {
String debugLine = theDebugLine.toString();
debugLine.compareTo(""); //$NON-NLS-1$
}
return theLine.toString();
}

View file

@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2006 Wind River Systems, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Martin Oberhuber (Wind River) - initial API and implementation
*******************************************************************************/
package org.eclipse.rse.services.ssh.shell;
import java.io.PrintWriter;
/**
* The SshShellWriterThread is a Thread used to print commands into
* a running ssh shell channel.
*
* A separate Thread is needed because the PipedInputStream
* used by ssh requirs that the writing end of the Pipe be
* a Thread that remains alive during the entire lifetime
* of the shell.
*/
public class SshShellWriterThread extends Thread
{
private PrintWriter fOutputWriter;
private String fNextCommand;
private boolean fIsCancelled;
/**
* constructor for ssh shell writer thread
* @param outputStream Stream to write to in separate Thread
*/
public SshShellWriterThread(PrintWriter outputWriter)
{
super();
fOutputWriter = outputWriter;
setName("Ssh ShellWriter"+getName()); //$NON-NLS-1$
start();
}
public synchronized boolean isDone()
{
return fIsCancelled;
}
public synchronized void stopThread()
{
fIsCancelled = true;
notifyAll();
}
/**
* Write command to remote side. Wait until the
* thread takes the command (no queueing).
* @param command to send
* @return boolean true if command was sent ok
*/
public synchronized boolean sendCommand(String command)
{
try {
//In case multiple commands try to send:
//wait until it's our turn
while (!fIsCancelled && fNextCommand!=null) {
wait();
}
if (!fIsCancelled) {
//Now it's our turn
fNextCommand = command;
notifyAll();
//Wait until our command is processed
while (!fIsCancelled && fNextCommand!=null) {
wait();
}
}
} catch(InterruptedException e) {
stopThread();
}
return !fIsCancelled;
}
public synchronized void run()
{
try {
while (!fIsCancelled) {
while (fNextCommand==null && !fIsCancelled) {
wait();
}
if (!fIsCancelled) {
fOutputWriter.println(fNextCommand);
fNextCommand=null;
notifyAll();
if (fOutputWriter.checkError()) { //flush AND get error
stopThread();
}
}
}
} catch(InterruptedException e) {
/* no special handling -> close stream */
} finally {
stopThread();
fOutputWriter.close();
fOutputWriter = null;
}
}
}