Copyright header update, 2015 edition
[deliverable/tracecompass.git] / org.eclipse.tracecompass.lttng2.control.ui / src / org / eclipse / tracecompass / internal / lttng2 / control / ui / views / remote / CommandShell.java
1 /**********************************************************************
2 * Copyright (c) 2012, 2015 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 * Markus Schorn - Bug 448058: Use org.eclipse.remote in favor of RSE
13 **********************************************************************/
14 package org.eclipse.tracecompass.internal.lttng2.control.ui.views.remote;
15
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;
24
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;
34
35 /**
36 * <p>
37 * Implementation of remote command execution using IRemoteConnection.
38 * </p>
39 *
40 * @author Patrick Tasse
41 * @author Bernd Hufmann
42 */
43 public class CommandShell implements ICommandShell {
44
45 // ------------------------------------------------------------------------
46 // Constants
47 // ------------------------------------------------------------------------
48
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$
55
56 // ------------------------------------------------------------------------
57 // Attributes
58 // ------------------------------------------------------------------------
59 private IRemoteConnection fConnection = null;
60 private final ExecutorService fExecutor = Executors.newFixedThreadPool(1);
61 private int fBackedByShell;
62
63 // ------------------------------------------------------------------------
64 // Constructors
65 // ------------------------------------------------------------------------
66
67 /**
68 * Create a new command shell
69 *
70 * @param connection the remote connection for this shell
71 */
72 public CommandShell(IRemoteConnection connection) {
73 fConnection = connection;
74 }
75
76 // ------------------------------------------------------------------------
77 // Operations
78 // ------------------------------------------------------------------------
79
80 @Override
81 public void connect() throws ExecutionException {
82 }
83
84 @Override
85 public void disconnect() {
86 fExecutor.shutdown();
87 }
88
89 @Override
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>() {
93 @Override
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())
98 && isBackedByShell();
99 IRemoteProcess process = startRemoteProcess(wrapCommand, command);
100 InputReader stdout = new InputReader(process.getInputStream());
101 InputReader stderr = new InputReader(process.getErrorStream());
102
103 try {
104 stdout.waitFor(monitor);
105 stderr.waitFor(monitor);
106 if (monitor == null || !monitor.isCanceled()) {
107 return createResult(wrapCommand, process.waitFor(), stdout.toString(), stderr.toString());
108 }
109 } catch (OperationCanceledException e) {
110 } catch (InterruptedException e) {
111 return new CommandResult(1, new String[0], new String[] {e.getMessage()});
112 } finally {
113 stdout.stop();
114 stderr.stop();
115 process.destroy();
116 }
117 }
118 return new CommandResult(1, new String[0], new String[] {"cancelled"}); //$NON-NLS-1$
119 }
120 });
121
122 fExecutor.execute(future);
123
124 try {
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);
132 } finally {
133 future.cancel(true);
134 }
135 }
136 throw new ExecutionException(Messages.TraceControl_ShellNotConnected, null);
137 }
138
139 private IRemoteProcess startRemoteProcess(boolean wrapCommand, List<String> command) throws IOException {
140 if (wrapCommand) {
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(' ');
146 }
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();
151 }
152
153 return fConnection.getProcessBuilder(command).start();
154 }
155
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;
163 try {
164 process = pb.start();
165 reader = new InputReader(process.getInputStream());
166 reader.waitFor(new NullProgressMonitor());
167 process.waitFor();
168
169 fBackedByShell = -1;
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)) {
173 fBackedByShell = 1;
174 }
175 } catch (IOException e) {
176 // On Windows, cannot start built-in echo command
177 fBackedByShell = -1;
178 } finally {
179 if (process != null) {
180 process.destroy();
181 }
182 if (reader != null) {
183 reader.stop();
184 }
185 }
186 }
187 return fBackedByShell == 1;
188 }
189
190 // ------------------------------------------------------------------------
191 // Helper methods
192 // ------------------------------------------------------------------------
193
194 private static CommandResult createResult(boolean isWrapped, int origResult, String origStdout, String origStderr) {
195 final int result;
196 final String stdout, stderr;
197 if (isWrapped) {
198 String[] holder = {origStdout};
199 result = unwrapOutput(holder);
200 stdout = holder[0];
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;
204 } else {
205 result = origResult;
206 stdout = origStdout;
207 stderr = origStderr;
208 }
209
210 String[] output = splitLines(stdout);
211 String[] error = splitLines(stderr);
212 return new CommandResult(result, output, error);
213 }
214
215 private static String[] splitLines(String output) {
216 if (output == null) {
217 return null;
218 }
219 return output.split("\\r?\\n"); //$NON-NLS-1$
220 }
221
222 private static int unwrapOutput(String[] outputHolder) {
223 String output = outputHolder[0];
224 int begin = skipEchoBeginTag(output);
225 begin = output.indexOf(BEGIN_TAG, begin);
226
227 if (begin < 0) {
228 outputHolder[0] = ""; //$NON-NLS-1$
229 return 1;
230 }
231
232 begin += BEGIN_TAG.length();
233 int end = output.indexOf(END_TAG, begin);
234 if (end < 0) {
235 outputHolder[0] = output.substring(begin).trim();
236 return 1;
237 }
238
239 outputHolder[0] = output.substring(begin, end).trim();
240 String tail = output.substring(end + END_TAG.length()).trim();
241 int numEnd;
242 for (numEnd = 0; numEnd < tail.length(); numEnd++) {
243 if (!Character.isDigit(tail.charAt(numEnd))) {
244 break;
245 }
246 }
247 try {
248 return Integer.parseInt(tail.substring(0, numEnd));
249 } catch (NumberFormatException e) {
250 return 1;
251 }
252 }
253
254 private static int skipEchoBeginTag(String output) {
255 final String searchFor = SHELL_ECHO_CMD + BEGIN_TAG;
256 int begin = 0;
257 for(;;) {
258 int i= output.indexOf(searchFor, begin);
259 if (i >= begin) {
260 begin = i + searchFor.length();
261 } else {
262 return begin;
263 }
264 }
265 }
266 }
This page took 0.039628 seconds and 5 git commands to generate.