lttng: add command execution timeout preference
[deliverable/tracecompass.git] / org.eclipse.linuxtools.lttng2.ui / src / org / eclipse / linuxtools / internal / lttng2 / ui / views / control / remote / CommandShell.java
1 /**********************************************************************
2 * Copyright (c) 2012, 2013 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 **********************************************************************/
13 package org.eclipse.linuxtools.internal.lttng2.ui.views.control.remote;
14
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;
27
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;
36
37 /**
38 * <p>
39 * Implementation of remote command execution using RSE's shell service.
40 * </p>
41 *
42 * @author Patrick Tasse
43 * @author Bernd Hufmann
44 */
45 public class CommandShell implements ICommandShell {
46
47 // ------------------------------------------------------------------------
48 // Constants
49 // ------------------------------------------------------------------------
50
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$
53
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$
56
57 /** Command delimiter for shell */
58 public final static String CMD_DELIMITER = "\n"; //$NON-NLS-1$
59
60 /** Shell "echo" command */
61 public final static String SHELL_ECHO_CMD = " echo "; //$NON-NLS-1$
62
63 /** Default command separator */
64 public final static char CMD_SEPARATOR = ';';
65
66 // ------------------------------------------------------------------------
67 // Attributes
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;
77
78 // ------------------------------------------------------------------------
79 // Constructors
80 // ------------------------------------------------------------------------
81
82 /**
83 * Create a new command shell
84 *
85 * @param proxy
86 * The RSE proxy for this shell
87 */
88 public CommandShell(IRemoteSystemProxy proxy) {
89 fProxy = proxy;
90 }
91
92 // ------------------------------------------------------------------------
93 // Operations
94 // ------------------------------------------------------------------------
95 /*
96 * (non-Javadoc)
97 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#connect()
98 */
99 @Override
100 public void connect() throws ExecutionException {
101 IShellService shellService = fProxy.getShellService();
102 Process p = null;
103 try {
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);
108 }
109 fInputBufferReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
110 fErrorBufferReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
111 fIsConnected = true;
112 }
113
114 /*
115 * (non-Javadoc)
116 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#disconnect()
117 */
118 @Override
119 public void disconnect() {
120 fIsConnected = false;
121 try {
122 fInputBufferReader.close();
123 fErrorBufferReader.close();
124 } catch (IOException e) {
125 // ignore
126 }
127 }
128
129 /*
130 * (non-Javadoc)
131 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
132 */
133 @Override
134 public ICommandResult executeCommand(String command, IProgressMonitor monitor) throws ExecutionException {
135 return executeCommand(command, monitor, true);
136 }
137
138 /*
139 * (non-Javadoc)
140 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor, boolean)
141 */
142 @Override
143 public ICommandResult executeCommand(final String command, final IProgressMonitor monitor, final boolean checkReturnValue) throws ExecutionException {
144 if (fIsConnected) {
145 FutureTask<CommandResult> future = new FutureTask<CommandResult>(new Callable<CommandResult>() {
146 @Override
147 public CommandResult call() throws IOException, CancellationException {
148 final ArrayList<String> result = new ArrayList<String>();
149
150 synchronized (fHostShell) {
151 // Initialize return value which will be updated in isAliasEchoResult()
152 fReturnValue = 0;
153
154 int startAlias = fRandom.nextInt();
155 int endAlias = fRandom.nextInt();
156 fHostShell.writeToShell(formatShellCommand(command, startAlias, endAlias));
157
158 String nextLine;
159 boolean isStartFound = false;
160 while ((nextLine = fInputBufferReader.readLine()) != null) {
161
162 if (monitor.isCanceled()) {
163 flushInput();
164 throw new CancellationException();
165 }
166
167 // check if line contains echoed start alias
168 if (isAliasEchoResult(nextLine, startAlias, true)) {
169 isStartFound = true;
170 continue;
171 }
172
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)) {
176 break;
177 }
178
179 // Ignore line if
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
183 if (!isStartFound ||
184 isCommandEcho(nextLine, command) ||
185 nextLine.contains(getEchoResult(endAlias)))
186 {
187 continue;
188 }
189
190 // Now it's time add to the result
191 result.add(nextLine);
192 }
193
194 // Read any left over output
195 flushInput();
196
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);
202 }
203 }
204 }
205 }
206 return new CommandResult(fReturnValue, result.toArray(new String[result.size()]));
207 }
208 });
209
210 fExecutor.execute(future);
211
212 try {
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);
220 }
221 }
222 throw new ExecutionException(Messages.TraceControl_ShellNotConnected, null);
223 }
224
225 // ------------------------------------------------------------------------
226 // Helper methods
227 // ------------------------------------------------------------------------
228 /**
229 * Flushes the buffer reader
230 * @throws IOException
231 */
232 private void flushInput() throws IOException {
233 char[] cbuf = new char[1];
234 while (fInputBufferReader.ready()) {
235 if (fInputBufferReader.read(cbuf, 0, 1) == -1) {
236 break;
237 }
238 }
239 }
240
241 /**
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.
245 *
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
250 */
251 private static String formatShellCommand(String cmd, int startAlias, int endAlias) {
252 if (cmd == null || cmd.equals("")) { //$NON-NLS-1$
253 return cmd;
254 }
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();
265 }
266
267 /**
268 * Creates a echo command line in the format: echo <start tag> <alias> <end tag> $?
269 *
270 * @param alias The command alias integer to be included in the echoed message.
271 * @return the echo command line
272 */
273 private static String getEchoCmd(int alias) {
274 return SHELL_ECHO_CMD + getEchoResult(alias) + "$?"; //$NON-NLS-1$
275 }
276
277 /**
278 * Creates the expected result for a given command alias:
279 * <start tag> <alias> <end tag> $?
280 *
281 * @param alias The command alias integer to be included in the echoed message.
282 * @return the expected echo result
283 */
284 private static String getEchoResult(int alias) {
285 return BEGIN_END_TAG + String.valueOf(alias) + DONE_MARKUP_STRING;
286 }
287
288 /**
289 * Verifies if given command line contains a command alias echo result.
290 *
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>
295 */
296 private boolean isAliasEchoResult(String line, int alias, boolean checkReturnValue) {
297 String expected = getEchoResult(alias);
298 if (line.startsWith(expected)) {
299 if (!checkReturnValue) {
300 try {
301 int k = Integer.valueOf(line.substring(expected.length()));
302 fReturnValue = k;
303 } catch (NumberFormatException e) {
304 // do nothing
305 }
306 }
307 return true;
308 }
309 int index = line.indexOf(expected);
310 if (index > 0) {
311 if (line.indexOf(SHELL_ECHO_CMD) == -1) {
312 return true;
313 }
314 }
315
316 return false;
317 }
318
319 /**
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.
324 *
325 * @param line
326 * The output line to verify
327 * @param cmd
328 * The command executed
329 * @return <code>true</code> if it's an echoed command line else
330 * <code>false</code>
331 */
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);
339 }
340 }
This page took 0.070894 seconds and 6 git commands to generate.