Skip to main content

This CGIT instance is deprecated, and repositories have been moved to Gitlab or Github. See the repository descriptions for specific locations.

summaryrefslogtreecommitdiffstats
blob: b741c3dac47b53146c948b5d6785507546adebe7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*******************************************************************************
 * Copyright (c) 2008 Wind River Systems, Inc. and others.
 * 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
 * Anna Dushistova  (MontaVista) - [170910] Integrate the TM Terminal View with RSE
 * Martin Oberhuber (Wind River) - [227320] Fix endless loop in SshTerminalShell
 *******************************************************************************/

package org.eclipse.rse.internal.services.ssh.terminal;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Hashtable;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.Session;

import org.eclipse.rse.internal.services.ssh.ISshSessionProvider;
import org.eclipse.rse.internal.services.terminals.AbstractTerminalShell;
import org.eclipse.rse.internal.services.terminals.ITerminalService;
import org.eclipse.rse.services.clientserver.PathUtility;
import org.eclipse.rse.services.clientserver.messages.SystemMessageException;
import org.eclipse.rse.services.files.RemoteFileException;

/**
 * A remote shell connection supporting Streams for I/O.
 */
public class SshTerminalShell extends AbstractTerminalShell {

	private ISshSessionProvider fSessionProvider;
	private Channel fChannel;
	private String fEncoding;
	private InputStream fInputStream;
	private OutputStream fOutputStream;
	private Writer fOutputStreamWriter;
	private int fWidth = 0;
	private int fHeight = 0;
	private static String defaultEncoding = new java.io.InputStreamReader(new java.io.ByteArrayInputStream(new byte[0])).getEncoding();

	/**
	 * Construct a new Terminal connection.
	 *
	 * The SSH channel is immediately connected in the Constructor.
	 *
	 * @param sessionProvider SSH session provider
	 * @param ptyType Terminal type to set, or <code>null</code> if not
	 *            relevant
	 * @param encoding The default encoding to use for initial command.
	 * @param environment Environment array to set, or <code>null</code> if
	 *            not relevant.
	 * @param initialWorkingDirectory initial directory to open the Terminal in.
	 *            Use <code>null</code> or empty String ("") to start in a
	 *            default directory. Empty String will typically start in the
	 *            home directory.
	 * @param commandToRun initial command to send.
	 * @throws SystemMessageException in case anything goes wrong. Channels and
	 *             Streams are all cleaned up again in this case.
	 * @see ITerminalService
	 */
	public SshTerminalShell(ISshSessionProvider sessionProvider, String ptyType, String encoding, String[] environment, String initialWorkingDirectory,
			String commandToRun) throws SystemMessageException {
		try {
			fSessionProvider = sessionProvider;
			fEncoding = encoding;
		    fChannel = fSessionProvider.getSession().openChannel("shell"); //$NON-NLS-1$
			if (ptyType != null && (fChannel instanceof ChannelShell)) {
			    //By default, jsch always creates a vt100 connection sized
			    //80x24 / 640x480 (dimensions can be changed).
		    	((ChannelShell) fChannel).setPtyType(ptyType);
		    }

		    //Try to set the user environment. On most sshd configurations, this will
		    //not work since in sshd_config, PermitUserEnvironment and AcceptEnv
		    //settings are disabled. Still, it's worth a try.
		    if (environment!=null && environment.length>0 && fChannel instanceof ChannelShell) {
		    	Hashtable envTable=new Hashtable();
		    	for(int i=0; i<environment.length; i++) {
		    		String curStr=environment[i];
		    		int curLen=environment[i].length();
		    		int idx = curStr.indexOf('=');
		    		if (idx>0 && idx<curLen-1) {
		    			String key=environment[i].substring(0, idx);
		    			String value=environment[i].substring(idx+1, curLen);
		    			if (fEncoding != null) {
		    				key = recode(key, fEncoding);
							value = recode(value, fEncoding);
		    			}
		    			envTable.put(key, value);
		    		}
		    	}
		    	((ChannelShell) fChannel).setEnv(envTable);
		    }

			fOutputStream = fChannel.getOutputStream();
			fInputStream = fChannel.getInputStream();
		    fChannel.connect();

		    if (fEncoding != null) {
		    	fOutputStreamWriter = new BufferedWriter(new OutputStreamWriter(fOutputStream, encoding));
			} else {
		    	// default encoding == System.getProperty("file.encoding")
				// TODO should try to determine remote encoding if possible
		    	fOutputStreamWriter = new BufferedWriter(new OutputStreamWriter(fOutputStream));
		    }

		    if (initialWorkingDirectory!=null && initialWorkingDirectory.length()>0
		    	&& !initialWorkingDirectory.equals(".") //$NON-NLS-1$
		    	&& !initialWorkingDirectory.equals("Command Shell") //$NON-NLS-1$ //FIXME workaround for bug 153047
		    ) {
			    writeToShell("cd " + PathUtility.enQuoteUnix(initialWorkingDirectory)); //$NON-NLS-1$
		    }
			if (commandToRun != null && commandToRun.length() > 0) {
		    	writeToShell(commandToRun);
		    }
		} catch(Exception e) {
			throw new RemoteFileException("Error creating Terminal", e); //$NON-NLS-1$
		} finally {
			isActive();
		}
	}

	public String getDefaultEncoding() {
		return fEncoding;
	}

	/**
	 * Encode String with requested user encoding, in case it differs from
	 * Platform default encoding.
	 *
	 * @param s String to encode
	 * @param encoding Encoding to use
	 * @return encoded String
	 * @throws UnsupportedEncodingException in case the requested encoding is
	 *             not supported
	 */
	protected String recode(String s, String encoding) throws UnsupportedEncodingException {
		if (encoding == null) {
			return s;
		} else if (encoding.equals(defaultEncoding)) {
			return s;
		}
		// what we want on the wire:
		byte[] bytes = s.getBytes(encoding);
		// what we need to tell Jsch to get this on the wire:
		return new String(bytes, defaultEncoding);
	}

	/*
	 * (non-Javadoc)
	 * @see ITerminalHostShell#getInputStream(Object)
	 */
	public InputStream getInputStream() {
		return fInputStream;
	}

	/*
	 * (non-Javadoc)
	 * @see ITerminalHostShell#getOutputStream(Object)
	 */
	public OutputStream getOutputStream() {
		return fOutputStream;
	}

	/**
	 * Write a command to the shell, honoring specified Encoding. Can only be
	 * done before an outputStream is obtained, since these commands would
	 * interfere with the outputStream.
	 *
	 * @param command Command String to send, or "#break" to send a Ctrl+C
	 *            command.
	 */
	public void writeToShell(String command) throws IOException {
		if (isActive()) {
			if ("#break".equals(command)) { //$NON-NLS-1$
				command = "\u0003"; // Unicode 3 == Ctrl+C //$NON-NLS-1$
			} else {
				command += "\r\n"; //$NON-NLS-1$
			}
			fOutputStreamWriter.write(command);
			fOutputStreamWriter.flush();
		}
	}

	public void exit() {
		if (fChannel != null) {
			try {
				try {
					getInputStream().close();
				} catch (IOException ioe) {
					/* ignore */
				}
				try {
					getOutputStream().close();
				} catch (IOException ioe) {
					/* ignore */
				}
				fChannel.disconnect();
			} finally {
				fChannel = null;
				isActive();
			}
		}
	}

	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();
		}
		return false;
	}

	public boolean isLocalEcho() {
		return false;
	}

	public void setTerminalSize(int newWidth, int newHeight) {
		if (fChannel != null && fChannel instanceof ChannelShell && (newWidth != fWidth || newHeight != fHeight)) {
			// avoid excessive communications due to change size requests by
			// caching previous size
			ChannelShell channelShell = (ChannelShell) fChannel;
			channelShell.setPtySize(newWidth, newHeight, 8 * newWidth, 8 * newHeight);
			fWidth = newWidth;
			fHeight = newHeight;
		}
	}

}

Back to the top