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:
parent
f2e6f47b3f
commit
a219176dd1
3 changed files with 190 additions and 35 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue