Fix for bug 383097 (CommandShell improvement)
[deliverable/tracecompass.git] / org.eclipse.linuxtools.lttng2.ui / src / org / eclipse / linuxtools / internal / lttng2 / ui / views / control / remote / CommandShell.java
1 /**********************************************************************
2 * Copyright (c) 2012 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Patrick Tasse - Initial API and implementation
11 * Bernd Hufmann - Updated using Executor Framework
12 **********************************************************************/
13 package org.eclipse.linuxtools.internal.lttng2.ui.views.control.remote;
14
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.util.ArrayList;
19 import java.util.Random;
20 import java.util.concurrent.Callable;
21 import java.util.concurrent.CancellationException;
22 import java.util.concurrent.ExecutorService;
23 import java.util.concurrent.Executors;
24 import java.util.concurrent.FutureTask;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27
28 import org.eclipse.core.commands.ExecutionException;
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.core.runtime.NullProgressMonitor;
31 import org.eclipse.linuxtools.internal.lttng2.ui.views.control.messages.Messages;
32 import org.eclipse.rse.services.shells.HostShellProcessAdapter;
33 import org.eclipse.rse.services.shells.IHostShell;
34 import org.eclipse.rse.services.shells.IShellService;
35
36 /**
37 * <p>
38 * Implementation of remote command execution using RSE's shell service.
39 * </p>
40 *
41 * @author Patrick Tasse
42 * @author Bernd Hufmann
43 */
44 public class CommandShell implements ICommandShell {
45
46 // ------------------------------------------------------------------------
47 // Constants
48 // ------------------------------------------------------------------------
49
50 /** Sub-string to be echo'ed when running command in shell, used to indicate that the command has finished running */
51 public final static String DONE_MARKUP_STRING = "--RSE:donedonedone:--"; //$NON-NLS-1$
52
53 /** Sub-string to be echoed when running a command in shell. */
54 public final static String BEGIN_END_TAG = "BEGIN-END-TAG:"; //$NON-NLS-1$
55
56 /** Command delimiter for shell */
57 public final static String CMD_DELIMITER = "\n"; //$NON-NLS-1$
58
59 /** Shell "echo" command */
60 public final static String SHELL_ECHO_CMD = " echo "; //$NON-NLS-1$
61
62 /** Default command separator */
63 public final static char CMD_SEPARATOR = ';';
64
65 // /** Default timeout value used for executing commands, in milliseconds */
66 private final static int DEFAULT_TIMEOUT_VALUE = 150000; // in milliseconds
67
68 // ------------------------------------------------------------------------
69 // Attributes
70 // ------------------------------------------------------------------------
71 private IRemoteSystemProxy fProxy = null;
72 private IHostShell fHostShell = null;
73 private BufferedReader fInputBufferReader = null;
74 private BufferedReader fErrorBufferReader = null;
75 private ExecutorService fExecutor = Executors.newFixedThreadPool(1);
76 private boolean fIsConnected = false;
77 private Random fRandom = new Random(System.currentTimeMillis());;
78 private int fReturnValue;
79
80 // ------------------------------------------------------------------------
81 // Constructors
82 // ------------------------------------------------------------------------
83 public CommandShell(IRemoteSystemProxy proxy) {
84 fProxy = proxy;
85 }
86
87 // ------------------------------------------------------------------------
88 // Operations
89 // ------------------------------------------------------------------------
90 /*
91 * (non-Javadoc)
92 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#connect()
93 */
94 @Override
95 public void connect() throws ExecutionException {
96 IShellService shellService = fProxy.getShellService();
97 Process p = null;
98 try {
99 fHostShell = shellService.launchShell("", new String[0], new NullProgressMonitor()); //$NON-NLS-1$
100 p = new HostShellProcessAdapter(fHostShell);
101 } catch (Exception e) {
102 throw new ExecutionException(Messages.TraceControl_CommandShellError, e);
103 }
104 fInputBufferReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
105 fErrorBufferReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
106 fIsConnected = true;
107 }
108
109 /*
110 * (non-Javadoc)
111 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#disconnect()
112 */
113 @Override
114 public void disconnect() {
115 fIsConnected = false;
116 try {
117 fInputBufferReader.close();
118 fErrorBufferReader.close();
119 } catch (IOException e) {
120 // ignore
121 }
122 }
123
124 /*
125 * (non-Javadoc)
126 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
127 */
128 @Override
129 public ICommandResult executeCommand(String command, IProgressMonitor monitor) throws ExecutionException {
130 return executeCommand(command, monitor, true);
131 }
132
133 /*
134 * (non-Javadoc)
135 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor, boolean)
136 */
137 @Override
138 public ICommandResult executeCommand(final String command, final IProgressMonitor monitor, final boolean checkReturnValue) throws ExecutionException {
139 if (fIsConnected) {
140 FutureTask<CommandResult> future = new FutureTask<CommandResult>(new Callable<CommandResult>() {
141 @Override
142 public CommandResult call() throws IOException, CancellationException {
143 final ArrayList<String> result = new ArrayList<String>();
144
145 synchronized (fHostShell) {
146 // Initialize return value which will be updated in isAliasEchoResult()
147 fReturnValue = 0;
148
149 int startAlias = fRandom.nextInt();
150 int endAlias = fRandom.nextInt();
151 fHostShell.writeToShell(formatShellCommand(command, startAlias, endAlias));
152
153 String nextLine;
154 boolean isStartFound = false;
155 while ((nextLine = fInputBufferReader.readLine()) != null) {
156
157 if (monitor.isCanceled()) {
158 flushInput();
159 throw new CancellationException();
160 }
161
162 // check if line contains echoed start alias
163 if (isAliasEchoResult(nextLine, startAlias, true)) {
164 isStartFound = true;
165 continue;
166 }
167
168 // check if line contains is the end mark-up. This will retrieve also
169 // the return value of the actual command.
170 if (isAliasEchoResult(nextLine, endAlias, false)) {
171 break;
172 }
173
174 // Ignore line if
175 // 1) start hasn't been found or
176 // 2) line is an echo of the command or
177 // 3) line is an echo of the end mark-up
178 if (!isStartFound ||
179 isCommandEcho(nextLine, command) ||
180 nextLine.contains(getEchoResult(endAlias)))
181 {
182 continue;
183 }
184
185 // Now it's time add to the result
186 result.add(nextLine);
187 }
188
189 // Read any left over output
190 flushInput();
191
192 // Read error stream output when command failed.
193 if (fReturnValue != 0) {
194 while(fErrorBufferReader.ready()) {
195 if ((nextLine = fErrorBufferReader.readLine()) != null) {
196 result.add(nextLine);
197 }
198 }
199 }
200 }
201 return new CommandResult(fReturnValue, result.toArray(new String[result.size()]));
202 }
203 });
204
205 fExecutor.execute(future);
206
207 try {
208 return future.get(DEFAULT_TIMEOUT_VALUE, TimeUnit.MILLISECONDS);
209 } catch (java.util.concurrent.ExecutionException ex) {
210 throw new ExecutionException(Messages.TraceControl_ExecutionFailure, ex);
211 } catch (InterruptedException ex) {
212 throw new ExecutionException(Messages.TraceControl_ExecutionCancelled, ex);
213 } catch (TimeoutException ex) {
214 throw new ExecutionException(Messages.TraceControl_ExecutionTimeout, ex);
215 }
216 }
217 throw new ExecutionException(Messages.TraceControl_ShellNotConnected, null);
218 }
219
220 // ------------------------------------------------------------------------
221 // Helper methods
222 // ------------------------------------------------------------------------
223 /**
224 * Flushes the buffer reader
225 * @throws IOException
226 */
227 private void flushInput() throws IOException {
228 char[] cbuf = new char[1];
229 while (fInputBufferReader.ready()) {
230 if (fInputBufferReader.read(cbuf, 0, 1) == -1) {
231 break;
232 }
233 }
234 }
235
236 /**
237 * Format the command to be sent into the shell command with start and end marker strings.
238 * The start marker is need to know when the actual command output starts. The end marker
239 * string is needed so we can tell that end of output has been reached.
240 *
241 * @param cmd The actual command
242 * @param startAlias The command alias for start marker
243 * @param endAlias The command alias for end marker
244 * @return formatted command string
245 */
246 private String formatShellCommand(String cmd, int startAlias, int endAlias) {
247 if (cmd == null || cmd.equals("")) { //$NON-NLS-1$
248 return cmd;
249 }
250 StringBuffer formattedCommand = new StringBuffer();
251 // Make multi-line command.
252 // Wrap actual command with start marker and end marker to wrap actual command.
253 formattedCommand.append(getEchoCmd(startAlias));
254 formattedCommand.append(CMD_DELIMITER);
255 formattedCommand.append(cmd);
256 formattedCommand.append(CMD_DELIMITER);
257 formattedCommand.append(getEchoCmd(endAlias));
258 formattedCommand.append(CMD_DELIMITER);
259 return formattedCommand.toString();
260 }
261
262 /**
263 * Creates a echo command line in the format: echo <start tag> <alias> <end tag> $?
264 *
265 * @param alias The command alias integer to be included in the echoed message.
266 * @return the echo command line
267 */
268 private String getEchoCmd(int alias) {
269 return SHELL_ECHO_CMD + getEchoResult(alias) + "$?"; //$NON-NLS-1$
270 }
271
272 /**
273 * Creates the expected result for a given command alias:
274 * <start tag> <alias> <end tag> $?
275 *
276 * @param alias The command alias integer to be included in the echoed message.
277 * @return the expected echo result
278 */
279 private String getEchoResult(int alias) {
280 return BEGIN_END_TAG + String.valueOf(alias) + DONE_MARKUP_STRING;
281 }
282
283 /**
284 * Verifies if given command line contains a command alias echo result.
285 *
286 * @param line The output line to test.
287 * @param alias The command alias
288 * @param checkReturnValue <code>true</code> to retrieve command result (previous command) <code>false</code>
289 * @return <code>true</code> if output line is a command alias echo result else <code>false</code>
290 */
291 private boolean isAliasEchoResult(String line, int alias, boolean checkReturnValue) {
292 String expected = getEchoResult(alias);
293 if (line.startsWith(expected)) {
294 if (!checkReturnValue) {
295 try {
296 int k = Integer.valueOf(line.substring(expected.length()));
297 fReturnValue = k;
298 } catch (NumberFormatException e) {
299 // do nothing
300 }
301 }
302 return true;
303 } else {
304 int index = line.indexOf(expected);
305 if (index > 0) {
306 if (line.indexOf(SHELL_ECHO_CMD) == -1) {
307 return true;
308 }
309 }
310 }
311
312 return false;
313 }
314
315 /**
316 * Verifies if output line is an echo of the given command line. If the output line is longer then
317 * the maximum line lengths (e.g. for ssh), the shell adds a line break character. This
318 * method takes this in consideration by comparing the command line without any whitespaces.
319 *
320 * @param line The output line to verify
321 * @param cmd The command executed
322 * @return <code>true</code> if it's an echoed command line else <code>false</code>
323 */
324 @SuppressWarnings("nls")
325 private boolean isCommandEcho(String line, String cmd) {
326 String s1 = line.replaceAll("\\s","");
327 String s2 = cmd.replaceAll("\\s","");
328 s2 = s2.replaceAll("(\\*)", "(\\\\*)");
329 String patternStr = ".*(" + s2 +")$";
330 return s1.matches(patternStr);
331 }
332 }
This page took 0.040701 seconds and 6 git commands to generate.