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