1 /**********************************************************************
2 * Copyright (c) 2012, 2013 Ericsson
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
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
;
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
;
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
.linuxtools
.internal
.lttng2
.ui
.views
.control
.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
;
39 * Implementation of remote command execution using RSE's shell service.
42 * @author Patrick Tasse
43 * @author Bernd Hufmann
45 public class CommandShell
implements ICommandShell
{
47 // ------------------------------------------------------------------------
49 // ------------------------------------------------------------------------
51 /** Sub-string to be echo'ed when running command in shell, used to indicate that the command has finished running */
52 public final static String DONE_MARKUP_STRING
= "--RSE:donedonedone:--"; //$NON-NLS-1$
54 /** Sub-string to be echoed when running a command in shell. */
55 public final static String BEGIN_END_TAG
= "BEGIN-END-TAG:"; //$NON-NLS-1$
57 /** Command delimiter for shell */
58 public final static String CMD_DELIMITER
= "\n"; //$NON-NLS-1$
60 /** Shell "echo" command */
61 public final static String SHELL_ECHO_CMD
= " echo "; //$NON-NLS-1$
63 /** Default command separator */
64 public final static char CMD_SEPARATOR
= ';';
66 // ------------------------------------------------------------------------
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
;
78 // ------------------------------------------------------------------------
80 // ------------------------------------------------------------------------
83 * Create a new command shell
86 * The RSE proxy for this shell
88 public CommandShell(IRemoteSystemProxy proxy
) {
92 // ------------------------------------------------------------------------
94 // ------------------------------------------------------------------------
97 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#connect()
100 public void connect() throws ExecutionException
{
101 IShellService shellService
= fProxy
.getShellService();
104 fHostShell
= shellService
.launchShell("", new String
[0], new NullProgressMonitor()); //$NON-NLS-1$
105 p
= new HostShellProcessAdapter(fHostShell
);
106 } catch (Exception e
) {
107 throw new ExecutionException(Messages
.TraceControl_CommandShellError
, e
);
109 fInputBufferReader
= new BufferedReader(new InputStreamReader(p
.getInputStream()));
110 fErrorBufferReader
= new BufferedReader(new InputStreamReader(p
.getErrorStream()));
116 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#disconnect()
119 public void disconnect() {
120 fIsConnected
= false;
122 fInputBufferReader
.close();
123 fErrorBufferReader
.close();
124 } catch (IOException e
) {
131 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
134 public ICommandResult
executeCommand(String command
, IProgressMonitor monitor
) throws ExecutionException
{
135 return executeCommand(command
, monitor
, true);
140 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor, boolean)
143 public ICommandResult
executeCommand(final String command
, final IProgressMonitor monitor
, final boolean checkReturnValue
) throws ExecutionException
{
145 FutureTask
<CommandResult
> future
= new FutureTask
<CommandResult
>(new Callable
<CommandResult
>() {
147 public CommandResult
call() throws IOException
, CancellationException
{
148 final ArrayList
<String
> result
= new ArrayList
<String
>();
150 synchronized (fHostShell
) {
151 // Initialize return value which will be updated in isAliasEchoResult()
154 int startAlias
= fRandom
.nextInt();
155 int endAlias
= fRandom
.nextInt();
156 fHostShell
.writeToShell(formatShellCommand(command
, startAlias
, endAlias
));
159 boolean isStartFound
= false;
160 while ((nextLine
= fInputBufferReader
.readLine()) != null) {
162 if (monitor
.isCanceled()) {
164 throw new CancellationException();
167 // check if line contains echoed start alias
168 if (isAliasEchoResult(nextLine
, startAlias
, true)) {
173 // check if line contains is the end mark-up. This will retrieve also
174 // the return value of the actual command.
175 if (isAliasEchoResult(nextLine
, endAlias
, false)) {
180 // 1) start hasn't been found or
181 // 2) line is an echo of the command or
182 // 3) line is an echo of the end mark-up
184 isCommandEcho(nextLine
, command
) ||
185 nextLine
.contains(getEchoResult(endAlias
)))
190 // Now it's time add to the result
191 result
.add(nextLine
);
194 // Read any left over output
197 // Read error stream output when command failed.
198 if (fReturnValue
!= 0) {
199 while(fErrorBufferReader
.ready()) {
200 if ((nextLine
= fErrorBufferReader
.readLine()) != null) {
201 result
.add(nextLine
);
206 return new CommandResult(fReturnValue
, result
.toArray(new String
[result
.size()]));
210 fExecutor
.execute(future
);
213 return future
.get(ControlPreferences
.getInstance().getCommandTimeout(), TimeUnit
.SECONDS
);
214 } catch (java
.util
.concurrent
.ExecutionException ex
) {
215 throw new ExecutionException(Messages
.TraceControl_ExecutionFailure
, ex
);
216 } catch (InterruptedException ex
) {
217 throw new ExecutionException(Messages
.TraceControl_ExecutionCancelled
, ex
);
218 } catch (TimeoutException ex
) {
219 throw new ExecutionException(Messages
.TraceControl_ExecutionTimeout
, ex
);
222 throw new ExecutionException(Messages
.TraceControl_ShellNotConnected
, null);
225 // ------------------------------------------------------------------------
227 // ------------------------------------------------------------------------
229 * Flushes the buffer reader
230 * @throws IOException
232 private void flushInput() throws IOException
{
233 char[] cbuf
= new char[1];
234 while (fInputBufferReader
.ready()) {
235 if (fInputBufferReader
.read(cbuf
, 0, 1) == -1) {
242 * Format the command to be sent into the shell command with start and end marker strings.
243 * The start marker is need to know when the actual command output starts. The end marker
244 * string is needed so we can tell that end of output has been reached.
246 * @param cmd The actual command
247 * @param startAlias The command alias for start marker
248 * @param endAlias The command alias for end marker
249 * @return formatted command string
251 private static String
formatShellCommand(String cmd
, int startAlias
, int endAlias
) {
252 if (cmd
== null || cmd
.equals("")) { //$NON-NLS-1$
255 StringBuffer formattedCommand
= new StringBuffer();
256 // Make multi-line command.
257 // Wrap actual command with start marker and end marker to wrap actual command.
258 formattedCommand
.append(getEchoCmd(startAlias
));
259 formattedCommand
.append(CMD_DELIMITER
);
260 formattedCommand
.append(cmd
);
261 formattedCommand
.append(CMD_DELIMITER
);
262 formattedCommand
.append(getEchoCmd(endAlias
));
263 formattedCommand
.append(CMD_DELIMITER
);
264 return formattedCommand
.toString();
268 * Creates a echo command line in the format: echo <start tag> <alias> <end tag> $?
270 * @param alias The command alias integer to be included in the echoed message.
271 * @return the echo command line
273 private static String
getEchoCmd(int alias
) {
274 return SHELL_ECHO_CMD
+ getEchoResult(alias
) + "$?"; //$NON-NLS-1$
278 * Creates the expected result for a given command alias:
279 * <start tag> <alias> <end tag> $?
281 * @param alias The command alias integer to be included in the echoed message.
282 * @return the expected echo result
284 private static String
getEchoResult(int alias
) {
285 return BEGIN_END_TAG
+ String
.valueOf(alias
) + DONE_MARKUP_STRING
;
289 * Verifies if given command line contains a command alias echo result.
291 * @param line The output line to test.
292 * @param alias The command alias
293 * @param checkReturnValue <code>true</code> to retrieve command result (previous command) <code>false</code>
294 * @return <code>true</code> if output line is a command alias echo result else <code>false</code>
296 private boolean isAliasEchoResult(String line
, int alias
, boolean checkReturnValue
) {
297 String expected
= getEchoResult(alias
);
298 if (line
.startsWith(expected
)) {
299 if (!checkReturnValue
) {
301 int k
= Integer
.valueOf(line
.substring(expected
.length()));
303 } catch (NumberFormatException e
) {
309 int index
= line
.indexOf(expected
);
311 if (line
.indexOf(SHELL_ECHO_CMD
) == -1) {
320 * Verifies if output line is an echo of the given command line. If the
321 * output line is longer then the maximum line lengths (e.g. for ssh), the
322 * shell adds a line break character. This method takes this in
323 * consideration by comparing the command line without any whitespaces.
326 * The output line to verify
328 * The command executed
329 * @return <code>true</code> if it's an echoed command line else
332 @SuppressWarnings("nls")
333 private static boolean isCommandEcho(String line
, String cmd
) {
334 String s1
= line
.replaceAll("\\s","");
335 String s2
= cmd
.replaceAll("\\s","");
336 s2
= s2
.replaceAll("(\\*)", "(\\\\*)");
337 String patternStr
= ".*(" + s2
+")$";
338 return s1
.matches(patternStr
);