1 /**********************************************************************
2 * Copyright (c) 2012, 2015 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 * Markus Schorn - Bug 448058: Use org.eclipse.remote in favor of RSE
13 **********************************************************************/
14 package org
.eclipse
.tracecompass
.internal
.lttng2
.control
.ui
.views
.remote
;
16 import java
.io
.IOException
;
17 import java
.util
.List
;
18 import java
.util
.concurrent
.Callable
;
19 import java
.util
.concurrent
.ExecutorService
;
20 import java
.util
.concurrent
.Executors
;
21 import java
.util
.concurrent
.FutureTask
;
22 import java
.util
.concurrent
.TimeUnit
;
23 import java
.util
.concurrent
.TimeoutException
;
25 import org
.eclipse
.core
.commands
.ExecutionException
;
26 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
27 import org
.eclipse
.core
.runtime
.NullProgressMonitor
;
28 import org
.eclipse
.core
.runtime
.OperationCanceledException
;
29 import org
.eclipse
.remote
.core
.IRemoteConnection
;
30 import org
.eclipse
.remote
.core
.IRemoteProcess
;
31 import org
.eclipse
.remote
.core
.IRemoteProcessBuilder
;
32 import org
.eclipse
.tracecompass
.internal
.lttng2
.control
.ui
.views
.messages
.Messages
;
33 import org
.eclipse
.tracecompass
.internal
.lttng2
.control
.ui
.views
.preferences
.ControlPreferences
;
37 * Implementation of remote command execution using IRemoteConnection.
40 * @author Patrick Tasse
41 * @author Bernd Hufmann
43 public class CommandShell
implements ICommandShell
{
45 // ------------------------------------------------------------------------
47 // ------------------------------------------------------------------------
49 private static final String BEGIN_TAG
= "org.eclipse.tracecompass-BEGIN-TAG:"; //$NON-NLS-1$
50 private static final String END_TAG
= "org.eclipse.tracecompass-END-TAG:"; //$NON-NLS-1$
51 private static final String RSE_ADAPTER_ID
= "org.eclipse.ptp.remote.RSERemoteServices"; //$NON-NLS-1$
52 private static final String SHELL_ECHO_CMD
= "echo "; //$NON-NLS-1$
53 private static final char CMD_SEPARATOR
= ';';
54 private static final String CMD_RESULT_VAR
= " $?"; //$NON-NLS-1$
56 // ------------------------------------------------------------------------
58 // ------------------------------------------------------------------------
59 private IRemoteConnection fConnection
= null;
60 private final ExecutorService fExecutor
= Executors
.newFixedThreadPool(1);
61 private int fBackedByShell
;
63 // ------------------------------------------------------------------------
65 // ------------------------------------------------------------------------
68 * Create a new command shell
70 * @param connection the remote connection for this shell
72 public CommandShell(IRemoteConnection connection
) {
73 fConnection
= connection
;
76 // ------------------------------------------------------------------------
78 // ------------------------------------------------------------------------
81 public void connect() throws ExecutionException
{
85 public void disconnect() {
90 public ICommandResult
executeCommand(final List
<String
> command
, final IProgressMonitor monitor
) throws ExecutionException
{
91 if (fConnection
.isOpen()) {
92 FutureTask
<CommandResult
> future
= new FutureTask
<>(new Callable
<CommandResult
>() {
94 public CommandResult
call() throws IOException
, InterruptedException
{
95 if (monitor
== null || !monitor
.isCanceled()) {
96 final boolean wrapCommand
=
97 RSE_ADAPTER_ID
.equals(fConnection
.getRemoteServices().getId())
99 IRemoteProcess process
= startRemoteProcess(wrapCommand
, command
);
100 InputReader stdout
= new InputReader(process
.getInputStream());
101 InputReader stderr
= new InputReader(process
.getErrorStream());
104 stdout
.waitFor(monitor
);
105 stderr
.waitFor(monitor
);
106 if (monitor
== null || !monitor
.isCanceled()) {
107 return createResult(wrapCommand
, process
.waitFor(), stdout
.toString(), stderr
.toString());
109 } catch (OperationCanceledException e
) {
110 } catch (InterruptedException e
) {
111 return new CommandResult(1, new String
[0], new String
[] {e
.getMessage()});
118 return new CommandResult(1, new String
[0], new String
[] {"cancelled"}); //$NON-NLS-1$
122 fExecutor
.execute(future
);
125 return future
.get(ControlPreferences
.getInstance().getCommandTimeout(), TimeUnit
.SECONDS
);
126 } catch (java
.util
.concurrent
.ExecutionException ex
) {
127 throw new ExecutionException(Messages
.TraceControl_ExecutionFailure
, ex
);
128 } catch (InterruptedException ex
) {
129 throw new ExecutionException(Messages
.TraceControl_ExecutionCancelled
, ex
);
130 } catch (TimeoutException ex
) {
131 throw new ExecutionException(Messages
.TraceControl_ExecutionTimeout
, ex
);
136 throw new ExecutionException(Messages
.TraceControl_ShellNotConnected
, null);
139 private IRemoteProcess
startRemoteProcess(boolean wrapCommand
, List
<String
> command
) throws IOException
{
141 StringBuilder formattedCommand
= new StringBuilder();
142 formattedCommand
.append(SHELL_ECHO_CMD
).append(BEGIN_TAG
);
143 formattedCommand
.append(CMD_SEPARATOR
);
144 for(String cmd
: command
) {
145 formattedCommand
.append(cmd
).append(' ');
147 formattedCommand
.append(CMD_SEPARATOR
);
148 formattedCommand
.append(SHELL_ECHO_CMD
).append(END_TAG
).append(CMD_RESULT_VAR
);
149 String
[] args
= formattedCommand
.toString().trim().split("\\s+"); //$NON-NLS-1$
150 return fConnection
.getProcessBuilder(args
).start();
153 return fConnection
.getProcessBuilder(command
).start();
156 private boolean isBackedByShell() throws InterruptedException
{
157 if (fBackedByShell
== 0) {
158 String cmd
= SHELL_ECHO_CMD
+ BEGIN_TAG
+ CMD_SEPARATOR
+ SHELL_ECHO_CMD
+ END_TAG
;
159 IRemoteProcessBuilder pb
= fConnection
.getProcessBuilder(cmd
.trim().split("\\s+")); //$NON-NLS-1$
160 pb
.redirectErrorStream(true);
161 IRemoteProcess process
= null;
162 InputReader reader
= null;
164 process
= pb
.start();
165 reader
= new InputReader(process
.getInputStream());
166 reader
.waitFor(new NullProgressMonitor());
170 String result
= reader
.toString();
171 int pos
= result
.indexOf(BEGIN_TAG
, skipEchoBeginTag(result
));
172 if (pos
>= 0 && result
.substring(pos
+ BEGIN_TAG
.length()).trim().startsWith(END_TAG
)) {
175 } catch (IOException e
) {
176 // On Windows, cannot start built-in echo command
179 if (process
!= null) {
182 if (reader
!= null) {
187 return fBackedByShell
== 1;
190 // ------------------------------------------------------------------------
192 // ------------------------------------------------------------------------
194 private static CommandResult
createResult(boolean isWrapped
, int origResult
, String origStdout
, String origStderr
) {
196 final String stdout
, stderr
;
198 String
[] holder
= {origStdout
};
199 result
= unwrapOutput(holder
);
201 // Workaround if error stream is not available and stderr output is written
202 // in standard output above. This is true for the SshTerminalShell implementation.
203 stderr
= origStderr
.isEmpty() ? stdout
: origStderr
;
210 String
[] output
= splitLines(stdout
);
211 String
[] error
= splitLines(stderr
);
212 return new CommandResult(result
, output
, error
);
215 private static String
[] splitLines(String output
) {
216 if (output
== null) {
219 return output
.split("\\r?\\n"); //$NON-NLS-1$
222 private static int unwrapOutput(String
[] outputHolder
) {
223 String output
= outputHolder
[0];
224 int begin
= skipEchoBeginTag(output
);
225 begin
= output
.indexOf(BEGIN_TAG
, begin
);
228 outputHolder
[0] = ""; //$NON-NLS-1$
232 begin
+= BEGIN_TAG
.length();
233 int end
= output
.indexOf(END_TAG
, begin
);
235 outputHolder
[0] = output
.substring(begin
).trim();
239 outputHolder
[0] = output
.substring(begin
, end
).trim();
240 String tail
= output
.substring(end
+ END_TAG
.length()).trim();
242 for (numEnd
= 0; numEnd
< tail
.length(); numEnd
++) {
243 if (!Character
.isDigit(tail
.charAt(numEnd
))) {
248 return Integer
.parseInt(tail
.substring(0, numEnd
));
249 } catch (NumberFormatException e
) {
254 private static int skipEchoBeginTag(String output
) {
255 final String searchFor
= SHELL_ECHO_CMD
+ BEGIN_TAG
;
258 int i
= output
.indexOf(searchFor
, begin
);
260 begin
= i
+ searchFor
.length();