blob: 814bd172110efd271ebda3d4292480fdbbf10dd8 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006 IBM Corporation.
* 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:
* IBM Corporation - Initial Implementation
*
*****************************************************************************/
package org.eclipse.ptp.remotetools.internal.ssh;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.ptp.remotetools.core.messages.Messages;
import org.eclipse.ptp.remotetools.exception.RemoteConnectionException;
import org.eclipse.ptp.remotetools.internal.common.Debug;
import org.eclipse.ptp.remotetools.utils.stream.ILineStreamListener;
import org.eclipse.ptp.remotetools.utils.stream.TextStreamObserver;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
public class ControlChannel implements ILineStreamListener {
/*
* Patterns recognized by the observer.
*/
private final static String markerPID = "PID="; //$NON-NLS-1$
private final static String markerPIID = "PIID="; //$NON-NLS-1$
private final static String markerSSH = "SSH_TTY="; //$NON-NLS-1$
private final Pattern pidPattern = Pattern.compile(markerPID + "(\\p{Digit}+) " + markerPIID + "(\\p{Digit}+)"); //$NON-NLS-1$ //$NON-NLS-2$
private final Pattern terminalPathPattern = Pattern.compile(markerSSH + "(/.+)"); //$NON-NLS-1$
// OutputStream that sends input to remote process input stream.
private OutputStream outputToControlTerminalInput;
private InputStream inputFromControlTerminalOutput;
// Control channel
private ChannelExec shell;
// Control terminal path.
private String controlTerminalPath;
// Control terminal observer thread.
private TextStreamObserver controlTerminalObserver;
/**
* Parent execution manager who will be notified about events from the
* control channel.
*/
private final Connection connection;
public ControlChannel(Connection connection) {
this.connection = connection;
}
public void open(IProgressMonitor monitor) throws RemoteConnectionException {
try {
// Open exec channel and alloc terminal
shell = connection.createExecChannel(false);
shell.setPty(true);
shell.setCommand("/bin/sh"); //$NON-NLS-1$
inputFromControlTerminalOutput = shell.getInputStream();
outputToControlTerminalInput = shell.getOutputStream();
shell.connect();
} catch (JSchException e) {
close();
throw new RemoteConnectionException(Messages.ControlChannel_Open_FailedCreateAuxiliaryShell, e);
} catch (IOException e) {
close();
throw new RemoteConnectionException(Messages.ControlChannel_Open_FailedCreateIOStream, e);
}
// Create stream observer and start it
controlTerminalObserver = new TextStreamObserver(inputFromControlTerminalOutput, this);
controlTerminalObserver.start();
try {
// Clean shell prompt.
outputToControlTerminalInput.write("export PS1=\n".getBytes()); //$NON-NLS-1$
// Write terminal path on control channel, to be read by the
// observer
String command = "/bin/sh -c 'echo \"" + markerSSH + "$SSH_TTY\"'\n"; //$NON-NLS-1$ //$NON-NLS-2$
outputToControlTerminalInput.write(command.getBytes());
outputToControlTerminalInput.flush();
} catch (IOException e) {
throw new RemoteConnectionException(Messages.ControlChannel_Open_FailedSendInitCommands, e);
}
// Wait until the channel answers the terminal path
Debug.println2(Messages.ControlChannel_Debug_StartedWaitingControlTerminalPath);
synchronized (this) {
while (!monitor.isCanceled() && controlTerminalPath == null) {
try {
this.wait(200);
} catch (InterruptedException e) {
throw new RemoteConnectionException(Messages.ControlChannel_Open_WaitControlTerminalPathInterrupted, e);
}
}
}
if (monitor.isCanceled()) {
close();
throw new RemoteConnectionException(Messages.ControlChannel_Open_WaitControlTerminalPathInterrupted);
}
Debug.println2(Messages.ControlChannel_Debug_ReceivedControlTerminalPath + controlTerminalPath);
}
public void newLine(String line) {
Debug.println2(Messages.ControlChannel_Debug_ControlConnectionReceived + line);
/*
* Test if the line contains a PID.
*/
Matcher pidmatch = pidPattern.matcher(line);
if (pidmatch.find()) {
String pid = pidmatch.group(1);
String piid = pidmatch.group(2);
connection.setPID(Integer.parseInt(piid), Integer.parseInt(pid));
return;
}
Matcher terminalPathMatcher = terminalPathPattern.matcher(line);
if (terminalPathMatcher.find()) {
synchronized (this) {
controlTerminalPath = terminalPathMatcher.group(1);
Debug.println2(Messages.ControlChannel_0 + controlTerminalPath);
this.notifyAll();
}
}
}
public void streamClosed() {
}
public void streamError(Exception e) {
Debug.println2(Messages.ControlChannel_Debug_ControlConnectionReceived + e);
}
public synchronized String getControlTerminalPath() {
return controlTerminalPath;
}
public void killRemoteProcess(int pid) {
if (pid > 0) {
try {
outputToControlTerminalInput.write(new String("kill -9 " + pid + "\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
outputToControlTerminalInput.flush();
} catch (IOException e) {
}
}
}
public synchronized String getKillablePrefix(int internaID) {
return "echo \"" + markerPID + "$$ " //$NON-NLS-1$ //$NON-NLS-2$
+ markerPIID + Integer.toString(internaID) + "\" > " + controlTerminalPath; //$NON-NLS-1$
}
public void close() {
if (controlTerminalObserver != null) {
controlTerminalObserver.kill();
}
controlTerminalObserver = null;
outputToControlTerminalInput = null;
inputFromControlTerminalOutput = null;
if (shell != null) {
shell.disconnect();
}
shell = null;
}
public boolean isConnected() {
return shell != null && shell.isConnected();
}
}