Commit | Line | Data |
---|---|---|
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 | |
b732adaa | 12 | * Markus Schorn - Bug 448058: Use org.eclipse.remote in favor of RSE |
eb1bab5b | 13 | **********************************************************************/ |
9bc60be7 | 14 | package org.eclipse.tracecompass.internal.lttng2.control.ui.views.remote; |
eb1bab5b | 15 | |
eb1bab5b | 16 | import java.io.IOException; |
eb1bab5b | 17 | import java.util.concurrent.Callable; |
eb1bab5b BH |
18 | import java.util.concurrent.ExecutorService; |
19 | import java.util.concurrent.Executors; | |
20 | import java.util.concurrent.FutureTask; | |
21 | import java.util.concurrent.TimeUnit; | |
22 | import java.util.concurrent.TimeoutException; | |
23 | ||
24 | import org.eclipse.core.commands.ExecutionException; | |
25 | import org.eclipse.core.runtime.IProgressMonitor; | |
26 | import org.eclipse.core.runtime.NullProgressMonitor; | |
b732adaa MS |
27 | import org.eclipse.core.runtime.OperationCanceledException; |
28 | import org.eclipse.remote.core.IRemoteConnection; | |
29 | import org.eclipse.remote.core.IRemoteProcess; | |
30 | import org.eclipse.remote.core.IRemoteProcessBuilder; | |
9bc60be7 AM |
31 | import org.eclipse.tracecompass.internal.lttng2.control.ui.views.messages.Messages; |
32 | import org.eclipse.tracecompass.internal.lttng2.control.ui.views.preferences.ControlPreferences; | |
eb1bab5b BH |
33 | |
34 | /** | |
eb1bab5b | 35 | * <p> |
b732adaa | 36 | * Implementation of remote command execution using IRemoteConnection. |
eb1bab5b | 37 | * </p> |
cfdb727a | 38 | * |
dbd4432d BH |
39 | * @author Patrick Tasse |
40 | * @author Bernd Hufmann | |
eb1bab5b BH |
41 | */ |
42 | public class CommandShell implements ICommandShell { | |
43 | ||
44 | // ------------------------------------------------------------------------ | |
45 | // Constants | |
46 | // ------------------------------------------------------------------------ | |
47 | ||
b732adaa MS |
48 | private static final String BEGIN_TAG = "org.eclipse.tracecompass-BEGIN-TAG:"; //$NON-NLS-1$ |
49 | private static final String END_TAG = "org.eclipse.tracecompass-END-TAG:"; //$NON-NLS-1$ | |
50 | private static final String RSE_ADAPTER_ID = "org.eclipse.ptp.remote.RSERemoteServices"; //$NON-NLS-1$ | |
51 | private static final String SHELL_ECHO_CMD = "echo "; //$NON-NLS-1$ | |
52 | private static final char CMD_SEPARATOR = ';'; | |
53 | private static final String CMD_RESULT_VAR = " $?"; //$NON-NLS-1$ | |
eb1bab5b | 54 | |
eb1bab5b BH |
55 | // ------------------------------------------------------------------------ |
56 | // Attributes | |
57 | // ------------------------------------------------------------------------ | |
b732adaa | 58 | private IRemoteConnection fConnection = null; |
ea21cd65 | 59 | private final ExecutorService fExecutor = Executors.newFixedThreadPool(1); |
b732adaa | 60 | private int fBackedByShell; |
ea21cd65 | 61 | |
eb1bab5b BH |
62 | // ------------------------------------------------------------------------ |
63 | // Constructors | |
64 | // ------------------------------------------------------------------------ | |
ea21cd65 AM |
65 | |
66 | /** | |
67 | * Create a new command shell | |
68 | * | |
b732adaa | 69 | * @param connection the remote connection for this shell |
ea21cd65 | 70 | */ |
b732adaa MS |
71 | public CommandShell(IRemoteConnection connection) { |
72 | fConnection = connection; | |
eb1bab5b BH |
73 | } |
74 | ||
75 | // ------------------------------------------------------------------------ | |
76 | // Operations | |
77 | // ------------------------------------------------------------------------ | |
11252342 | 78 | |
eb1bab5b BH |
79 | @Override |
80 | public void connect() throws ExecutionException { | |
eb1bab5b BH |
81 | } |
82 | ||
eb1bab5b BH |
83 | @Override |
84 | public void disconnect() { | |
b732adaa | 85 | fExecutor.shutdown(); |
eb1bab5b BH |
86 | } |
87 | ||
eb1bab5b | 88 | @Override |
b732adaa MS |
89 | public ICommandResult executeCommand(final String command, final IProgressMonitor monitor) throws ExecutionException { |
90 | if (fConnection.isOpen()) { | |
e0838ca1 | 91 | FutureTask<CommandResult> future = new FutureTask<>(new Callable<CommandResult>() { |
eb1bab5b | 92 | @Override |
b732adaa MS |
93 | public CommandResult call() throws IOException, InterruptedException { |
94 | if (monitor == null || !monitor.isCanceled()) { | |
95 | final boolean wrapCommand = | |
96 | RSE_ADAPTER_ID.equals(fConnection.getRemoteServices().getId()) | |
97 | && isBackedByShell(); | |
98 | IRemoteProcess process = startRemoteProcess(wrapCommand, command); | |
99 | InputReader stdout = new InputReader(process.getInputStream()); | |
100 | InputReader stderr = new InputReader(process.getErrorStream()); | |
101 | ||
102 | try { | |
103 | stdout.waitFor(monitor); | |
104 | stderr.waitFor(monitor); | |
105 | if (monitor == null || !monitor.isCanceled()) { | |
106 | return createResult(wrapCommand, process.waitFor(), stdout.toString(), stderr.toString()); | |
1a18ada9 | 107 | } |
b732adaa MS |
108 | } catch (OperationCanceledException e) { |
109 | } catch (InterruptedException e) { | |
110 | return new CommandResult(1, new String[0], new String[] {e.getMessage()}); | |
111 | } finally { | |
112 | stdout.stop(); | |
113 | stderr.stop(); | |
114 | process.destroy(); | |
d6fc6e1b | 115 | } |
eb1bab5b | 116 | } |
b732adaa | 117 | return new CommandResult(1, new String[0], new String[] {"cancelled"}); //$NON-NLS-1$ |
eb1bab5b BH |
118 | } |
119 | }); | |
120 | ||
121 | fExecutor.execute(future); | |
122 | ||
123 | try { | |
4bdf5f96 | 124 | return future.get(ControlPreferences.getInstance().getCommandTimeout(), TimeUnit.SECONDS); |
eb1bab5b BH |
125 | } catch (java.util.concurrent.ExecutionException ex) { |
126 | throw new ExecutionException(Messages.TraceControl_ExecutionFailure, ex); | |
127 | } catch (InterruptedException ex) { | |
128 | throw new ExecutionException(Messages.TraceControl_ExecutionCancelled, ex); | |
129 | } catch (TimeoutException ex) { | |
130 | throw new ExecutionException(Messages.TraceControl_ExecutionTimeout, ex); | |
b732adaa MS |
131 | } finally { |
132 | future.cancel(true); | |
eb1bab5b BH |
133 | } |
134 | } | |
135 | throw new ExecutionException(Messages.TraceControl_ShellNotConnected, null); | |
136 | } | |
cfdb727a | 137 | |
b732adaa MS |
138 | private IRemoteProcess startRemoteProcess(boolean wrapCommand, String command) throws IOException { |
139 | String outputCommand = command; | |
140 | if (wrapCommand) { | |
141 | StringBuilder formattedCommand = new StringBuilder(); | |
142 | formattedCommand.append(SHELL_ECHO_CMD).append(BEGIN_TAG); | |
143 | formattedCommand.append(CMD_SEPARATOR); | |
144 | formattedCommand.append(command); | |
145 | formattedCommand.append(CMD_SEPARATOR); | |
146 | formattedCommand.append(SHELL_ECHO_CMD).append(END_TAG).append(CMD_RESULT_VAR); | |
147 | outputCommand = formattedCommand.toString(); | |
148 | } | |
149 | ||
150 | String[] args = outputCommand.trim().split("\\s+"); //$NON-NLS-1$ | |
151 | return fConnection.getProcessBuilder(args).start(); | |
152 | } | |
153 | ||
154 | private boolean isBackedByShell() throws InterruptedException { | |
155 | if (fBackedByShell == 0) { | |
156 | String cmd= SHELL_ECHO_CMD + BEGIN_TAG + CMD_SEPARATOR + SHELL_ECHO_CMD + END_TAG; | |
157 | IRemoteProcessBuilder pb = fConnection.getProcessBuilder(cmd.trim().split("\\s+")); //$NON-NLS-1$ | |
158 | pb.redirectErrorStream(true); | |
159 | IRemoteProcess process = null; | |
160 | InputReader reader = null; | |
161 | try { | |
162 | process = pb.start(); | |
163 | reader = new InputReader(process.getInputStream()); | |
164 | reader.waitFor(new NullProgressMonitor()); | |
165 | process.waitFor(); | |
166 | ||
167 | fBackedByShell = -1; | |
168 | String result= reader.toString(); | |
169 | int pos = result.indexOf(BEGIN_TAG, skipEchoBeginTag(result)); | |
170 | if (pos >= 0 && result.substring(pos + BEGIN_TAG.length()).trim().startsWith(END_TAG)) { | |
171 | fBackedByShell = 1; | |
172 | } | |
173 | } catch (IOException e) { | |
174 | // On Windows, cannot start built-in echo command | |
175 | fBackedByShell = -1; | |
176 | } finally { | |
177 | if (process != null) { | |
178 | process.destroy(); | |
179 | } | |
180 | if (reader != null) { | |
181 | reader.stop(); | |
182 | } | |
183 | } | |
184 | } | |
185 | return fBackedByShell == 1; | |
186 | } | |
187 | ||
eb1bab5b BH |
188 | // ------------------------------------------------------------------------ |
189 | // Helper methods | |
190 | // ------------------------------------------------------------------------ | |
b732adaa MS |
191 | |
192 | private static CommandResult createResult(boolean isWrapped, int origResult, String origStdout, String origStderr) { | |
193 | final int result; | |
194 | final String stdout, stderr; | |
195 | if (isWrapped) { | |
196 | String[] holder = {origStdout}; | |
197 | result = unwrapOutput(holder); | |
198 | stdout = holder[0]; | |
199 | // Workaround if error stream is not available and stderr output is written | |
200 | // in standard output above. This is true for the SshTerminalShell implementation. | |
201 | stderr = origStderr.isEmpty() ? stdout : origStderr; | |
202 | } else { | |
203 | result = origResult; | |
204 | stdout = origStdout; | |
205 | stderr = origStderr; | |
eb1bab5b | 206 | } |
b732adaa MS |
207 | |
208 | String[] output = splitLines(stdout); | |
209 | String[] error = result == 0 ? null : splitLines(stderr); | |
210 | return new CommandResult(result, output, error); | |
eb1bab5b | 211 | } |
cfdb727a | 212 | |
b732adaa MS |
213 | private static String[] splitLines(String output) { |
214 | if (output == null) { | |
215 | return null; | |
cfdb727a | 216 | } |
b732adaa | 217 | return output.split("\\r?\\n"); //$NON-NLS-1$ |
eb1bab5b | 218 | } |
ea21cd65 | 219 | |
b732adaa MS |
220 | private static int unwrapOutput(String[] outputHolder) { |
221 | String output = outputHolder[0]; | |
222 | int begin = skipEchoBeginTag(output); | |
223 | begin = output.indexOf(BEGIN_TAG, begin); | |
d6fc6e1b | 224 | |
b732adaa MS |
225 | if (begin < 0) { |
226 | outputHolder[0] = ""; //$NON-NLS-1$ | |
227 | return 1; | |
228 | } | |
ea21cd65 | 229 | |
b732adaa MS |
230 | begin += BEGIN_TAG.length(); |
231 | int end = output.indexOf(END_TAG, begin); | |
232 | if (end < 0) { | |
233 | outputHolder[0] = output.substring(begin).trim(); | |
234 | return 1; | |
235 | } | |
236 | ||
237 | outputHolder[0] = output.substring(begin, end).trim(); | |
238 | String tail = output.substring(end + END_TAG.length()).trim(); | |
239 | int numEnd; | |
240 | for (numEnd = 0; numEnd < tail.length(); numEnd++) { | |
241 | if (!Character.isDigit(tail.charAt(numEnd))) { | |
242 | break; | |
d6fc6e1b | 243 | } |
ea21cd65 | 244 | } |
b732adaa MS |
245 | try { |
246 | return Integer.parseInt(tail.substring(0, numEnd)); | |
247 | } catch (NumberFormatException e) { | |
248 | return 1; | |
d6fc6e1b | 249 | } |
d6fc6e1b | 250 | } |
ea21cd65 | 251 | |
b732adaa MS |
252 | private static int skipEchoBeginTag(String output) { |
253 | final String searchFor = SHELL_ECHO_CMD + BEGIN_TAG; | |
254 | int begin = 0; | |
255 | for(;;) { | |
256 | int i= output.indexOf(searchFor, begin); | |
257 | if (i >= begin) { | |
258 | begin = i + searchFor.length(); | |
259 | } else { | |
260 | return begin; | |
261 | } | |
262 | } | |
d6fc6e1b | 263 | } |
eb1bab5b | 264 | } |