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