OSDN Git Service

00a65be64c737d883eeeb539b12100495336b798
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / console / shell / ShellConsole.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.cyanogenmod.filemanager.console.shell;
18
19 import android.content.Context;
20 import android.util.Log;
21
22 import com.cyanogenmod.filemanager.FileManagerApplication;
23 import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
24 import com.cyanogenmod.filemanager.commands.Executable;
25 import com.cyanogenmod.filemanager.commands.ExecutableFactory;
26 import com.cyanogenmod.filemanager.commands.GroupsExecutable;
27 import com.cyanogenmod.filemanager.commands.IdentityExecutable;
28 import com.cyanogenmod.filemanager.commands.ProcessIdExecutable;
29 import com.cyanogenmod.filemanager.commands.SIGNAL;
30 import com.cyanogenmod.filemanager.commands.shell.AsyncResultProgram;
31 import com.cyanogenmod.filemanager.commands.shell.Command;
32 import com.cyanogenmod.filemanager.commands.shell.InvalidCommandDefinitionException;
33 import com.cyanogenmod.filemanager.commands.shell.Program;
34 import com.cyanogenmod.filemanager.commands.shell.Shell;
35 import com.cyanogenmod.filemanager.commands.shell.ShellExecutableFactory;
36 import com.cyanogenmod.filemanager.commands.shell.SyncResultProgram;
37 import com.cyanogenmod.filemanager.console.CommandNotFoundException;
38 import com.cyanogenmod.filemanager.console.Console;
39 import com.cyanogenmod.filemanager.console.ConsoleAllocException;
40 import com.cyanogenmod.filemanager.console.ExecutionException;
41 import com.cyanogenmod.filemanager.console.InsufficientPermissionsException;
42 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
43 import com.cyanogenmod.filemanager.console.OperationTimeoutException;
44 import com.cyanogenmod.filemanager.console.ReadOnlyFilesystemException;
45 import com.cyanogenmod.filemanager.model.Identity;
46 import com.cyanogenmod.filemanager.util.CommandHelper;
47 import com.cyanogenmod.filemanager.util.FileHelper;
48
49 import java.io.ByteArrayOutputStream;
50 import java.io.File;
51 import java.io.FileNotFoundException;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.OutputStream;
55 import java.security.SecureRandom;
56 import java.text.ParseException;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.List;
60
61 /**
62  * An implementation of a {@link Console} based in the execution of shell commands.<br/>
63  * <br/>
64  * This class holds a <code>shell bash</code> program associated with the application, being
65  * a wrapper to execute all other programs (like shell does in linux), capturing the
66  * output (stdin and stderr) and the exit code of the program executed.
67  */
68 public abstract class ShellConsole extends Console implements Program.ProgramListener {
69
70     private static final String TAG = "ShellConsole"; //$NON-NLS-1$
71
72     // A timeout of 3 seconds should be enough for no-debugging environments
73     private static final long DEFAULT_TIMEOUT =
74             FileManagerApplication.isDebuggable() ? 20000L : 3000L;
75
76     // A maximum operation timeout independently of the isWaitOnNewDataReceipt
77     // of the program. A synchronous operation must not be more longer than
78     // MAX_OPERATION_TIMEOUT + DEFAULT_TIMEOUT
79     private static final long MAX_OPERATION_TIMEOUT = 30000L;
80
81     private static final int DEFAULT_BUFFER = 512;
82
83     //Shell References
84     private final Shell mShell;
85     private Identity mIdentity;
86
87     //Process References
88     private final Object mSync = new Object();
89     /**
90      * @hide
91      */
92     final Object mPartialSync = new Object();
93     /**
94      * @hide
95      */
96     boolean mActive = false;
97     private boolean mFinished = true;
98     private boolean mNewData = false;
99     private Process mProc = null;
100     /**
101      * @hide
102      */
103     Program mActiveCommand = null;
104     /**
105      * @hide
106      */
107     boolean mCancelled;
108     /**
109      * @hide
110      */
111     boolean mStarted;
112
113     //Buffers
114     private InputStream mIn = null;
115     private InputStream mErr = null;
116     private OutputStream mOut = null;
117     /**
118      * @hide
119      */
120     ByteArrayOutputStream mSbIn = null;
121     /**
122      * @hide
123      */
124     ByteArrayOutputStream mSbErr = null;
125
126     private final SecureRandom mRandom;
127
128     private ControlPatternInfo mControlPattern = new ControlPatternInfo();
129     private static class ControlPatternInfo {
130         String mStartId1, mStartId2;
131         String mEndId1, mEndId2;
132         byte[] mStartControlBytes;
133         int mEndControlLength;
134
135         public void setNewPattern(String s1, String s2, String e1, String e2) {
136             mStartId1 = s1;
137             mStartId2 = s2;
138             mEndId1 = e1;
139             mEndId2 = e2;
140             mStartControlBytes = (mStartId1 + "0" + mStartId2).getBytes();
141             mEndControlLength = mEndId1.length() + mEndId2.length() + 3; // leave 3 for exit code;
142         }
143
144         public byte[] getStartControlPatternBytes() {
145             return mStartControlBytes;
146         }
147
148         public int getEndControlPatternLength() {
149             return mEndControlLength;
150         }
151
152         public int[] getEndControlMatch(byte[] bytes) {
153             return getEndControlMatch(bytes, false);
154         }
155
156         /**
157          * Find end control pattern indices
158          * @param bytes byte array to check against
159          * @param getExitIndices if true, this method will return the indices of the values
160          *                       in between the end control pattern (e.g. exit code), otherwise
161          *                       it will return the start and end indices of the whole end pattern
162          * @return if {@code getExitIndices} is false, See {@link #findControlPattern(byte[], byte[])}
163          *         otherwise it will return values as described in {@code getExitIndices}
164          */
165         public int[] getEndControlMatch(byte[] bytes, boolean getExitIndices) {
166             // in the end control pattern, we check for endId1, 1-3 chars for exit code, endId2
167             int start, end;
168
169             final int[] end1PatternResult = findControlPattern(bytes, mEndId1.getBytes());
170             if (end1PatternResult != null) {
171                 start = end1PatternResult[0];
172
173                 final int[] end2PatternResult = findControlPattern(bytes, mEndId2.getBytes());
174                 if (end2PatternResult != null) {
175                     if (getExitIndices) {
176                         return new int[]{end1PatternResult[1], end2PatternResult[0]};
177                     }
178
179                     end = end2PatternResult[1];
180                     return new int[]{start, end};
181                 }
182             }
183             return null;
184         }
185
186         /**
187          * Find the start control pattern indices
188          * @param bytes to check against
189          * @return See {@link #findControlPattern(byte[], byte[])}
190          */
191         public int[] getStartControlMatch(byte[] bytes) {
192             return findControlPattern(bytes, getStartControlPatternBytes());
193         }
194
195         /**
196          * Checks whether {@code controlBytes} exists inside of {@code bytes} and returns the start
197          * and end indices if it does. Returns null otherwise.
198          *
199          * @param bytes where to look for the pattern
200          * @param controlBytes the pattern to search for
201          * @return if {@code controlBytes} was found inside of {@code bytes} then this method will
202          * return an int[] array of length 2:
203          * <ul>
204          * <li>[0] the starting index of the control pattern
205          * <li>[1] the ending index of the control pattern + 1 (like {{@link java.lang.String#substring(int, int)})
206          * </ul>
207          */
208         private int[] findControlPattern(byte[] bytes, byte[] controlBytes) {
209             if (bytes.length >= controlBytes.length) {
210                 for (int i = 0; i < bytes.length; i++) {
211                     boolean foundControlBytePattern = true;
212                     int patternEnd = -1;
213                     if (bytes[i] == controlBytes[0]) {
214                         int start = i;
215                         for (int j = start; j < controlBytes.length + start; j++) {
216                             if (j > bytes.length - 1) {
217                                 foundControlBytePattern = false;
218                                 break;
219                             }
220                             if (bytes[j] != controlBytes[j - start]) {
221                                 foundControlBytePattern = false;
222                                 break;
223                             } else {
224                                 patternEnd = j;
225                             }
226                         }
227                         if (foundControlBytePattern) {
228                             return new int[]{start, patternEnd+1};
229                         }
230                     }
231                 }
232             }
233             return null;
234         }
235     }
236
237     /**
238      * @hide
239      */
240     int mBufferSize;
241
242     private final ShellExecutableFactory mExecutableFactory;
243
244     /**
245      * Constructor of <code>ShellConsole</code>.
246      *
247      * @param shell The shell used to execute commands
248      * @throws FileNotFoundException If the initial directory not exists
249      * @throws IOException If initial directory couldn't be resolved
250      */
251     public ShellConsole(Shell shell)
252             throws FileNotFoundException, IOException {
253         super();
254         this.mShell = shell;
255         this.mExecutableFactory = new ShellExecutableFactory(this);
256
257         this.mBufferSize = DEFAULT_BUFFER;
258
259         //Restart the buffers
260         this.mSbIn = new ByteArrayOutputStream();
261         this.mSbErr = new ByteArrayOutputStream();
262
263         //Generate an aleatory secure random generator
264         try {
265             this.mRandom = SecureRandom.getInstance("SHA1PRNG"); //$NON-NLS-1$
266         } catch (Exception ex) {
267             throw new IOException(ex);
268         }
269     }
270
271     /**
272      * {@inheritDoc}
273      */
274     @Override
275     public ExecutableFactory getExecutableFactory() {
276         return this.mExecutableFactory;
277     }
278
279     /**
280      * {@inheritDoc}
281      */
282     @Override
283     public Identity getIdentity() {
284         return this.mIdentity;
285     }
286
287     /**
288      * Method that returns the buffer size
289      *
290      * @return int The buffer size
291      */
292     public int getBufferSize() {
293         return this.mBufferSize;
294     }
295
296     /**
297      * Method that sets the buffer size
298      *
299      * @param bufferSize the The buffer size
300      */
301     public void setBufferSize(int bufferSize) {
302         this.mBufferSize = bufferSize;
303     }
304
305     /**
306      * {@inheritDoc}
307      */
308     @Override
309     public final boolean isActive() {
310         return this.mActive;
311     }
312
313     /**
314      * {@inheritDoc}
315      */
316     @Override
317     public final void alloc() throws ConsoleAllocException {
318         try {
319             //Create command string
320             List<String> cmd = new ArrayList<String>();
321             cmd.add(this.mShell.getCommand());
322             if (this.mShell.getArguments() != null && this.mShell.getArguments().length() > 0) {
323                 cmd.add(this.mShell.getArguments());
324             }
325
326             //Create the process
327             Runtime rt = Runtime.getRuntime();
328             this.mProc =
329                     rt.exec(
330                             cmd.toArray(new String[cmd.size()]),
331                             this.mShell.getEnvironment(),
332                             new File(FileHelper.ROOT_DIRECTORY).getCanonicalFile());
333             synchronized (this.mSync) {
334                 this.mActive = true;
335             }
336             if (isTrace()) {
337                 Log.v(TAG,
338                         String.format("Create console %s, command: %s, args: %s, env: %s",  //$NON-NLS-1$
339                                 this.mShell.getId(),
340                                 this.mShell.getCommand(),
341                                 this.mShell.getArguments(),
342                                 Arrays.toString(this.mShell.getEnvironment())));
343             }
344
345             //Allocate buffers
346             this.mIn = this.mProc.getInputStream();
347             this.mErr = this.mProc.getErrorStream();
348             this.mOut = this.mProc.getOutputStream();
349             if (this.mIn == null || this.mErr == null || this.mOut == null) {
350                 try {
351                     dealloc();
352                 } catch (Throwable ex) {
353                     /**NON BLOCK**/
354                 }
355                 throw new ConsoleAllocException("Console buffer allocation error."); //$NON-NLS-1$
356             }
357
358             //Starts a thread for extract output, and check timeout
359             createStdInThread(this.mIn);
360             createStdErrThread(this.mErr);
361
362             //Wait for thread start
363             Thread.sleep(50L);
364
365             //Check if process its active
366             checkIfProcessExits();
367             synchronized (this.mSync) {
368                 if (!this.mActive) {
369                     throw new ConsoleAllocException("Shell not started."); //$NON-NLS-1$
370                 }
371             }
372
373             // Retrieve the PID of the shell
374             ProcessIdExecutable processIdCmd =
375                     getExecutableFactory().
376                         newCreator().createShellProcessIdExecutable();
377             // Wait indefinitely if the console is allocating a su command. We need to
378             // wait to user response to SuperUser or SuperSu prompt (or whatever it is)
379             // The rest of sync operations will run with a timeout.
380             execute(processIdCmd, this.isPrivileged());
381             Integer pid = null;
382             try {
383                 pid = processIdCmd.getResult().get(0);
384             } catch (Exception e) {
385                 // Ignore
386             }
387             if (pid == null) {
388                 throw new ConsoleAllocException(
389                         "can't retrieve the PID of the shell."); //$NON-NLS-1$
390             }
391             this.mShell.setPid(pid.intValue());
392
393             //Retrieve identity
394             IdentityExecutable identityCmd =
395                     getExecutableFactory().newCreator().createIdentityExecutable();
396             execute(identityCmd, null);
397             this.mIdentity = identityCmd.getResult();
398             // Identity command is required for root console detection,
399             // but Groups command is not used for now. Also, this command is causing
400             // problems on some implementations (maybe toolbox?) which don't
401             // recognize the root AID and returns an error. Safely ignore on error.
402             try {
403                 if (this.mIdentity.getGroups().size() == 0) {
404                     //Try with groups
405                     GroupsExecutable groupsCmd =
406                             getExecutableFactory().newCreator().createGroupsExecutable();
407                     execute(groupsCmd, null);
408                     this.mIdentity.setGroups(groupsCmd.getResult());
409                 }
410             } catch (Exception ex) {
411                 Log.w(TAG, "Groups command failed. Ignored.", ex); //$NON-NLS-1$
412             }
413
414         } catch (Exception ex) {
415             try {
416                 dealloc();
417             } catch (Throwable ex2) {
418                 /**NON BLOCK**/
419             }
420             throw new ConsoleAllocException("Console allocation error.", ex); //$NON-NLS-1$
421         }
422
423     }
424
425     /**
426      * {@inheritDoc}
427      */
428     @Override
429     public final void dealloc() {
430         synchronized (this.mSync) {
431             if (this.mActive) {
432                 this.mActive = false;
433                 this.mFinished = true;
434
435                 //Close buffers
436                 try {
437                     if (this.mIn != null) {
438                         this.mIn.close();
439                     }
440                 } catch (Throwable ex) {
441                     /**NON BLOCK**/
442                 }
443                 try {
444                     if (this.mErr != null) {
445                         this.mErr.close();
446                     }
447                 } catch (Throwable ex) {
448                     /**NON BLOCK**/
449                 }
450                 try {
451                     if (this.mOut != null) {
452                         this.mOut.close();
453                     }
454                 } catch (Throwable ex) {
455                     /**NON BLOCK**/
456                 }
457                 try {
458                     this.mProc.destroy();
459                 } catch (Throwable e) {/**NON BLOCK**/}
460                 this.mIn = null;
461                 this.mErr = null;
462                 this.mOut = null;
463                 this.mSbIn = null;
464                 this.mSbErr = null;
465             }
466         }
467     }
468
469     /**
470      * {@inheritDoc}
471      */
472     @Override
473     public final void realloc() throws ConsoleAllocException {
474         dealloc();
475         alloc();
476     }
477
478     /**
479      * {@inheritDoc}
480      */
481     @Override
482     public synchronized void execute(Executable executable, Context ctx)
483             throws ConsoleAllocException, InsufficientPermissionsException, NoSuchFileOrDirectory,
484             OperationTimeoutException, ExecutionException, CommandNotFoundException,
485             ReadOnlyFilesystemException {
486         execute(executable, false);
487     }
488
489     /**
490      * Method for execute a command in the operating system layer.
491      *
492      * @param executable The executable command to be executed
493      * @param waitForSu Wait for su (do not used timeout)
494      * @throws ConsoleAllocException If the console is not allocated
495      * @throws InsufficientPermissionsException If an operation requires elevated permissions
496      * @throws NoSuchFileOrDirectory If the file or directory was not found
497      * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
498      * @throws CommandNotFoundException If the executable program was not found
499      * @throws ExecutionException If the operation returns a invalid exit code
500      * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
501      */
502     private synchronized void execute(final Executable executable, final boolean waitForSu)
503             throws ConsoleAllocException, InsufficientPermissionsException,
504             CommandNotFoundException, NoSuchFileOrDirectory,
505             OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException {
506
507         //Is a program?
508         if (!(executable instanceof Program)) {
509             throw new CommandNotFoundException("executable not instanceof Program"); //$NON-NLS-1$
510         }
511
512         //Asynchronous or synchronous execution?
513         final Program program = (Program)executable;
514         if (executable instanceof AsyncResultExecutable) {
515             Thread asyncThread = new Thread(new Runnable() {
516                 @Override
517                 public void run() {
518                     //Synchronous execution (but asynchronous running in a thread)
519                     //This way syncExecute is locked until this thread ends
520                     try {
521                         //Synchronous execution (2 tries with 1 reallocation)
522                         final ShellConsole shell = ShellConsole.this;
523                         if (shell.syncExecute(program, true, false)) {
524                             shell.syncExecute(program, false, false);
525                         }
526                     } catch (Exception ex) {
527                         if (((AsyncResultExecutable)executable).getAsyncResultListener() != null) {
528                             ((AsyncResultExecutable)executable).
529                                 getAsyncResultListener().onException(ex);
530                         } else {
531                             //Capture exception
532                             Log.e(TAG, "Fail asynchronous execution", ex); //$NON-NLS-1$
533                         }
534                     }
535                 }
536             });
537             asyncThread.start();
538         } else {
539             //Synchronous execution (2 tries with 1 reallocation)
540             program.setExitOnStdErrOutput(waitForSu);
541             if (syncExecute(program, true, waitForSu) && !waitForSu) {
542                 syncExecute(program, false, false);
543             }
544         }
545     }
546
547     /**
548      * Method for execute a program command in the operating system layer in a synchronous way.
549      *
550      * @param program The program to execute
551      * @param reallocate If the console must be reallocated on i/o error
552      * @param waitForSu Wait for su (do not used timeout)
553      * @return boolean If the console was reallocated
554      * @throws ConsoleAllocException If the console is not allocated
555      * @throws InsufficientPermissionsException If an operation requires elevated permissions
556      * @throws CommandNotFoundException If the command was not found
557      * @throws NoSuchFileOrDirectory If the file or directory was not found
558      * @throws OperationTimeoutException If the operation exceeded the maximum time of wait
559      * @throws ExecutionException If the operation returns a invalid exit code
560      * @throws ReadOnlyFilesystemException If the operation writes in a read-only filesystem
561      * @hide
562      */
563     synchronized boolean syncExecute(
564             final Program program, boolean reallocate, boolean waitForSu)
565             throws ConsoleAllocException, InsufficientPermissionsException,
566             CommandNotFoundException, NoSuchFileOrDirectory,
567             OperationTimeoutException, ExecutionException, ReadOnlyFilesystemException {
568
569         try {
570             //Check the console status before send command
571             checkConsole();
572
573             synchronized (this.mSync) {
574                 if (!this.mActive) {
575                     throw new ConsoleAllocException("No console allocated"); //$NON-NLS-1$
576                 }
577             }
578
579             //Saves the active command reference
580             this.mActiveCommand = program;
581             final boolean async = program instanceof AsyncResultProgram;
582
583             //Reset the buffers
584             this.mStarted = false;
585             this.mCancelled = false;
586             this.mSbIn = new ByteArrayOutputStream();
587             this.mSbErr = new ByteArrayOutputStream();
588
589             //Random start/end identifiers
590             String startId1 =
591                     String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
592             String startId2 =
593                     String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
594             String endId1 =
595                     String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
596             String endId2 =
597                     String.format("/#%d#/", Long.valueOf(this.mRandom.nextLong())); //$NON-NLS-1$
598
599             //Create command string
600             String cmd = program.getCommand();
601             String args = program.getArguments();
602
603             //Audit command
604             if (isTrace()) {
605                 Log.v(TAG,
606                         String.format("%s-%s, command: %s, args: %s",  //$NON-NLS-1$
607                                 this.mShell.getId(),
608                                 program.getId(),
609                                 cmd,
610                                 args));
611             }
612
613             //Is asynchronous program? Then set asynchronous
614             program.setProgramListener(this);
615             if (async) {
616                 ((AsyncResultProgram)program).setOnCancelListener(this);
617                 ((AsyncResultProgram)program).setOnEndListener(this);
618             }
619
620             //Send the command + a control code with exit code
621             //The process has finished where control control code is present.
622             //This control code is unique in every invocation and is secure random
623             //generated (control code 1 + exit code + control code 2)
624             try {
625                 boolean hasEndControl = (!(program instanceof AsyncResultProgram) ||
626                                            (program instanceof AsyncResultProgram &&
627                                             ((AsyncResultProgram)program).isExpectEnd()));
628                 mControlPattern.setNewPattern(startId1, startId2, endId1, endId2);
629
630                 String startCmd =
631                         Command.getStartCodeCommandInfo(
632                                 FileManagerApplication.getInstance().getResources());
633                 startCmd = String.format(
634                         startCmd, "'" + startId1 +//$NON-NLS-1$
635                         "'", "'" + startId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
636                 String endCmd =
637                         Command.getExitCodeCommandInfo(
638                                 FileManagerApplication.getInstance().getResources());
639                 endCmd = String.format(
640                         endCmd, "'" + endId1 + //$NON-NLS-1$
641                         "'", "'" + endId2 + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
642                 StringBuilder sb = new StringBuilder()
643                     .append(startCmd)
644                     .append(" ")  //$NON-NLS-1$
645                     .append(cmd)
646                     .append(" ")  //$NON-NLS-1$
647                     .append(args);
648                if (hasEndControl) {
649                    sb = sb.append(" ") //$NON-NLS-1$
650                           .append(endCmd);
651                }
652                sb.append(FileHelper.NEWLINE);
653                synchronized (this.mSync) {
654                    this.mFinished = false;
655                    this.mNewData = false;
656                    this.mOut.write(sb.toString().getBytes());
657                }
658             } catch (InvalidCommandDefinitionException icdEx) {
659                 throw new CommandNotFoundException(
660                         "ExitCodeCommandInfo not found", icdEx); //$NON-NLS-1$
661             }
662
663             //Now, wait for buffers to be filled
664             synchronized (this.mSync) {
665                 if (!this.mFinished) {
666                     if (waitForSu || program.isIndefinitelyWait()) {
667                         this.mSync.wait();
668                     } else {
669                         final long start = System.currentTimeMillis();
670                         while (true) {
671                             this.mSync.wait(DEFAULT_TIMEOUT);
672                             if (!this.mFinished) {
673                                 final long end = System.currentTimeMillis();
674                                 if (!program.isWaitOnNewDataReceipt() ||
675                                     !this.mNewData ||
676                                     (end - start >= MAX_OPERATION_TIMEOUT)) {
677                                     throw new OperationTimeoutException(end - start, cmd);
678                                 }
679
680                                 // Still waiting for program ending
681                                 this.mNewData = false;
682                                 continue;
683                             }
684                             break;
685                         }
686                     }
687                 }
688             }
689
690             //End partial results?
691             if (async) {
692                 synchronized (this.mPartialSync) {
693                     ((AsyncResultProgram)program).onRequestEndParsePartialResult(this.mCancelled);
694                 }
695             }
696
697             //Retrieve exit code
698             int exitCode = getExitCode(this.mSbIn, async);
699             if (async) {
700                 synchronized (this.mPartialSync) {
701                     ((AsyncResultProgram)program).onRequestExitCode(exitCode);
702                 }
703             }
704             if (isTrace()) {
705                 Log.v(TAG,
706                         String.format("%s-%s, command: %s, exitCode: %s",  //$NON-NLS-1$
707                                 this.mShell.getId(),
708                                 program.getId(),
709                                 cmd,
710                                 String.valueOf(exitCode)));
711             }
712
713             //Check if invocation was successfully or not
714             if (!program.isIgnoreShellStdErrCheck()) {
715                 //Wait for stderr buffer to be filled
716                 if (exitCode != 0) {
717                     try {
718                         Thread.sleep(100L);
719                     } catch (Throwable ex) {/**NON BLOCK**/}
720                 }
721                 this.mShell.checkStdErr(this.mActiveCommand, exitCode, this.mSbErr.toString());
722             }
723             this.mShell.checkExitCode(exitCode);
724             program.checkExitCode(exitCode);
725             program.checkStdErr(exitCode, this.mSbErr.toString());
726
727             //Parse the result? Only if not partial results
728             if (program instanceof SyncResultProgram) {
729                 try {
730                     ((SyncResultProgram)program).parse(
731                             this.mSbIn.toString(), this.mSbErr.toString());
732                 } catch (ParseException pEx) {
733                     throw new ExecutionException(
734                             "SyncResultProgram parse failed", pEx); //$NON-NLS-1$
735                 }
736             }
737
738             //Invocation finished. Now program.getResult() has the result of
739             //the operation, if any exists
740
741         } catch (OperationTimeoutException otEx) {
742             try {
743                 killCurrentCommand();
744             } catch (Exception e) { /**NON BLOCK **/}
745             throw otEx;
746
747         } catch (IOException ioEx) {
748             if (reallocate) {
749                 realloc();
750                 return true;
751             }
752             throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$
753
754         } catch (InterruptedException ioEx) {
755             if (reallocate) {
756                 realloc();
757                 return true;
758             }
759             throw new ExecutionException("Console allocation error.", ioEx); //$NON-NLS-1$
760
761         } finally {
762             //Dereference the active command
763             this.mActiveCommand = null;
764         }
765
766         //Operation complete
767         return false;
768     }
769
770     /**
771      * Method that creates the standard input thread for read program response.
772      *
773      * @param in The standard input buffer
774      * @return Thread The standard input thread
775      */
776     private Thread createStdInThread(final InputStream in) {
777         Thread t = new Thread(new Runnable() {
778             @SuppressWarnings("synthetic-access")
779             @Override
780             public void run() {
781                 final ShellConsole shell = ShellConsole.this;
782                 int read = 0;
783                 ByteArrayOutputStream sb = null;
784                 try {
785                     while (shell.mActive) {
786                         //Read only one byte with active wait
787                         final int r = in.read();
788                         if (r == -1) {
789                             break;
790                         }
791
792                         // Type of command
793                         boolean async =
794                                 shell.mActiveCommand != null &&
795                                 shell.mActiveCommand instanceof AsyncResultProgram;
796                         if (!async || sb == null) {
797                             sb = new ByteArrayOutputStream();
798                         }
799
800                         if (!shell.mCancelled) {
801                             shell.mSbIn.write(r);
802                             if (!shell.mStarted) {
803                                 shell.mStarted = isCommandStarted(shell.mSbIn);
804                                 if (shell.mStarted) {
805                                     byte[] bytes = shell.mSbIn.toByteArray();
806                                     sb = new ByteArrayOutputStream();
807                                     sb.write(bytes, 0, bytes.length);
808                                     if (async) {
809                                         synchronized (shell.mPartialSync) {
810                                             ((AsyncResultProgram)
811                                                     shell.mActiveCommand).
812                                                     onRequestStartParsePartialResult();
813                                         }
814                                     }
815                                 } else {
816                                     byte[] data = shell.mSbIn.toByteArray();
817                                     sb.write(data, 0, data.length);
818                                 }
819                             } else {
820                                 sb.write(r);
821                             }
822
823                             // New data received
824                             onNewData();
825
826                             //Check if the command has finished (and extract the control)
827                             boolean finished = isCommandFinished(shell.mSbIn, sb);
828
829                             //Notify asynchronous partial data
830                             if (shell.mStarted && async) {
831                                 AsyncResultProgram program =
832                                         ((AsyncResultProgram)shell.mActiveCommand);
833                                 String partial = sb.toString();
834                                 if (partial.length() >= shell.mControlPattern
835                                         .getEndControlPatternLength()) {
836                                     program.onRequestParsePartialResult(sb.toByteArray());
837                                     shell.toStdIn(partial);
838
839                                     // Reset the temp buffer
840                                     sb = new ByteArrayOutputStream();
841                                 }
842                             }
843
844                             if (finished) {
845                                 if (!async) {
846                                     shell.toStdIn(String.valueOf((char)r));
847                                 } else {
848                                     AsyncResultProgram program =
849                                             ((AsyncResultProgram)shell.mActiveCommand);
850                                     String partial = sb.toString();
851                                     if (program != null) {
852                                         program.onRequestParsePartialResult(sb.toByteArray());
853                                     }
854                                     shell.toStdIn(partial);
855                                 }
856
857                                 //Notify the end
858                                 notifyProcessFinished();
859                                 break;
860                             }
861                             if (!async && !finished) {
862                                 shell.toStdIn(String.valueOf((char)r));
863                             }
864                         }
865
866                         //Has more data? Read with available as more as exists
867                         //or maximum loop count is rebased
868                         int count = 0;
869                         while (in.available() > 0 && count < 10) {
870                             count++;
871                             int available =
872                                     Math.min(in.available(), shell.mBufferSize);
873                             byte[] data = new byte[available];
874                             read = in.read(data);
875
876                             // Type of command
877                             async =
878                                     shell.mActiveCommand != null &&
879                                     shell.mActiveCommand instanceof AsyncResultProgram;
880
881                             // Exit if active command is cancelled
882                             if (shell.mCancelled) continue;
883
884                             final String s = new String(data, 0, read);
885                             shell.mSbIn.write(data, 0, read);
886                             if (!shell.mStarted) {
887                                 shell.mStarted = isCommandStarted(shell.mSbIn);
888                                 if (shell.mStarted) {
889                                     byte[] bytes = shell.mSbIn.toByteArray();
890                                     sb = new ByteArrayOutputStream();
891                                     sb.write(bytes, 0, bytes.length);
892                                     if (async) {
893                                         synchronized (shell.mPartialSync) {
894                                             AsyncResultProgram p =
895                                                     ((AsyncResultProgram)shell.mActiveCommand);
896                                             if (p != null) {
897                                                 p.onRequestStartParsePartialResult();
898                                             }
899                                         }
900                                     }
901                                 } else {
902                                     byte[] bytes = shell.mSbIn.toByteArray();
903                                     sb.write(bytes, 0, bytes.length);
904                                 }
905                             } else {
906                                 sb.write(data, 0, read);
907                             }
908
909                             // New data received
910                             onNewData();
911
912                             //Check if the command has finished (and extract the control)
913                             boolean finished = isCommandFinished(shell.mSbIn, sb);
914
915                             //Notify asynchronous partial data
916                             if (async) {
917                                 AsyncResultProgram program = ((AsyncResultProgram)shell.mActiveCommand);
918                                 String partial = sb.toString();
919                                 if (partial.length() >=  shell.mControlPattern
920                                         .getEndControlPatternLength()) {
921                                     if (program != null) {
922                                         program.onRequestParsePartialResult(sb.toByteArray());
923                                     }
924                                     shell.toStdIn(partial);
925
926                                     // Reset the temp buffer
927                                     sb = new ByteArrayOutputStream();
928                                 }
929                             }
930
931                             if (finished) {
932                                 if (!async) {
933                                     shell.toStdIn(s);
934                                 } else {
935                                     AsyncResultProgram program =
936                                             ((AsyncResultProgram)shell.mActiveCommand);
937                                     String partial = sb.toString();
938                                     if (program != null) {
939                                         program.onRequestParsePartialResult(sb.toByteArray());
940                                     }
941                                     shell.toStdIn(partial);
942                                 }
943
944                                 //Notify the end
945                                 notifyProcessFinished();
946                                 break;
947                             }
948                             if (!async && !finished) {
949                                 shell.toStdIn(s);
950                             }
951
952                             //Wait for buffer to be filled
953                             try {
954                                 Thread.sleep(1L);
955                             } catch (Throwable ex) {/**NON BLOCK**/}
956                         }
957
958                         //Asynchronous programs can cause a lot of output, control buffers
959                         //for a low memory footprint
960                         if (async) {
961                             trimBuffer(shell.mSbIn);
962                             trimBuffer(shell.mSbErr);
963                         }
964
965                         //Check if process has exited
966                         checkIfProcessExits();
967                     }
968                 } catch (Exception ioEx) {
969                     notifyProcessExit(ioEx);
970                 }
971             }
972         });
973         t.setName(String.format("%s", "stdin")); //$NON-NLS-1$//$NON-NLS-2$
974         t.start();
975         return t;
976     }
977
978     /**
979      * Method that echoes the stdin
980      *
981      * @param stdin The buffer of the stdin
982      * @hide
983      */
984     void toStdIn(String stdin) {
985         //Audit (if not cancelled)
986         if (!this.mCancelled && isTrace() && stdin.length() > 0) {
987             Log.v(TAG,
988                     String.format(
989                             "stdin: %s", stdin)); //$NON-NLS-1$
990         }
991     }
992
993     /**
994      * Method that creates the standard error thread for read program response.
995      *
996      * @param err The standard error buffer
997      * @return Thread The standard error thread
998      */
999     private Thread createStdErrThread(final InputStream err) {
1000         Thread t = new Thread(new Runnable() {
1001             @Override
1002             public void run() {
1003                 final ShellConsole shell = ShellConsole.this;
1004                 int read = 0;
1005                 try {
1006                     while (shell.mActive) {
1007                         //Read only one byte with active wait
1008                         int r = err.read();
1009                         if (r == -1) {
1010                             break;
1011                         }
1012
1013                         // Has the process received something that we dont expect?
1014                         if (shell.mActiveCommand != null &&
1015                             shell.mActiveCommand.isExitOnStdErrOutput()) {
1016                             notifyProcessFinished();
1017                             continue;
1018                         }
1019
1020                         // Type of command
1021                         boolean async =
1022                                 shell.mActiveCommand != null &&
1023                                 shell.mActiveCommand instanceof AsyncResultProgram;
1024
1025                         ByteArrayOutputStream sb = new ByteArrayOutputStream();
1026                         if (!shell.mCancelled) {
1027                             shell.mSbErr.write(r);
1028                             sb.write(r);
1029
1030                             //Notify asynchronous partial data
1031                             if (shell.mStarted && async) {
1032                                 AsyncResultProgram program =
1033                                         ((AsyncResultProgram)shell.mActiveCommand);
1034                                 if (program != null) {
1035                                     program.parsePartialErrResult(new String(new char[]{(char)r}));
1036                                 }
1037                             }
1038
1039                             toStdErr(sb.toString());
1040                         }
1041
1042                         // New data received
1043                         onNewData();
1044
1045                         //Has more data? Read with available as more as exists
1046                         //or maximum loop count is rebased
1047                         int count = 0;
1048                         while (err.available() > 0 && count < 10) {
1049                             count++;
1050                             int available = Math.min(err.available(), shell.mBufferSize);
1051                             byte[] data = new byte[available];
1052                             read = err.read(data);
1053
1054                             // Type of command
1055                             async =
1056                                     shell.mActiveCommand != null &&
1057                                     shell.mActiveCommand instanceof AsyncResultProgram;
1058
1059                             // Add to stderr
1060                             String s = new String(data, 0, read);
1061                             shell.mSbErr.write(data, 0, read);
1062                             sb.write(data, 0, read);
1063
1064                             //Notify asynchronous partial data
1065                             if (async) {
1066                                 AsyncResultProgram program =
1067                                         ((AsyncResultProgram)shell.mActiveCommand);
1068                                 if (program != null) {
1069                                     program.parsePartialErrResult(s);
1070                                 }
1071                             }
1072                             toStdErr(s);
1073
1074                             // Has the process received something that we dont expect?
1075                             if (shell.mActiveCommand != null &&
1076                                 shell.mActiveCommand.isExitOnStdErrOutput()) {
1077                                 notifyProcessFinished();
1078                                 break;
1079                             }
1080
1081                             // New data received
1082                             onNewData();
1083
1084                             //Wait for buffer to be filled
1085                             try {
1086                                 Thread.sleep(1L);
1087                             } catch (Throwable ex) {
1088                                 /**NON BLOCK**/
1089                             }
1090                         }
1091
1092                         //Asynchronous programs can cause a lot of output, control buffers
1093                         //for a low memory footprint
1094                         if (shell.mActiveCommand != null &&
1095                                 shell.mActiveCommand instanceof AsyncResultProgram) {
1096                             trimBuffer(shell.mSbIn);
1097                             trimBuffer(shell.mSbErr);
1098                         }
1099                     }
1100                 } catch (Exception ioEx) {
1101                     notifyProcessExit(ioEx);
1102                 }
1103             }
1104         });
1105         t.setName(String.format("%s", "stderr")); //$NON-NLS-1$//$NON-NLS-2$
1106         t.start();
1107         return t;
1108     }
1109
1110     /**
1111      * Method that echoes the stderr
1112      *
1113      * @param stdin The buffer of the stderr
1114      * @hide
1115      */
1116     void toStdErr(String stderr) {
1117         //Audit (if not cancelled)
1118         if (!this.mCancelled && isTrace()) {
1119             Log.v(TAG,
1120                     String.format(
1121                             "stderr: %s", stderr)); //$NON-NLS-1$
1122         }
1123     }
1124
1125     /**
1126      * Method that checks the console status and restart the console
1127      * if this is unusable.
1128      *
1129      * @throws ConsoleAllocException If the console can't be reallocated
1130      */
1131     private void checkConsole() throws ConsoleAllocException {
1132         try {
1133             //Test write something to the buffer
1134             this.mOut.write(FileHelper.NEWLINE.getBytes());
1135             this.mOut.write(FileHelper.NEWLINE.getBytes());
1136         } catch (IOException ioex) {
1137             //Something is wrong with the buffers. Reallocate console.
1138             Log.w(TAG,
1139                 "Something is wrong with the console buffers. Reallocate console.", //$NON-NLS-1$
1140                 ioex);
1141
1142             //Reallocate the damage console
1143             realloc();
1144         }
1145     }
1146
1147     /**
1148      * Method that verifies if the process had exited.
1149      * @hide
1150      */
1151     void checkIfProcessExits() {
1152         try {
1153             if (this.mProc != null) {
1154                 synchronized (this.mSync) {
1155                     this.mProc.exitValue();
1156                 }
1157                 this.mActive = false; //Exited
1158             }
1159         } catch (IllegalThreadStateException itsEx) {
1160             //Not exited
1161         }
1162     }
1163
1164     /**
1165      * Method that notifies the ending of the process.
1166      *
1167      * @param ex The exception, only if the process exit with a exception.
1168      * Otherwise null
1169      * @hide
1170      */
1171     void notifyProcessExit(Exception ex) {
1172         synchronized (this.mSync) {
1173             if (this.mActive) {
1174                 this.mActive = false;
1175                 this.mFinished = true;
1176                 this.mSync.notify();
1177                 if (ex != null) {
1178                     Log.w(TAG, "Exit with exception", ex); //$NON-NLS-1$
1179                 }
1180             }
1181         }
1182     }
1183
1184     /**
1185      * Method that notifies the ending of the command execution.
1186      * @hide
1187      */
1188     void notifyProcessFinished() {
1189         synchronized (this.mSync) {
1190             if (this.mActive) {
1191                 this.mFinished = true;
1192                 this.mSync.notify();
1193             }
1194         }
1195     }
1196
1197     /**
1198      * Method that returns if the command has started by checking the
1199      * standard input buffer. This method also removes the control start command
1200      * from the buffer, if it's present, leaving in the buffer the new data bytes.
1201      *
1202      * @param stdin The standard in buffer
1203      * @return boolean If the command has started
1204      * @hide
1205      */
1206     boolean isCommandStarted(ByteArrayOutputStream stdin) {
1207         if (stdin == null) return false;
1208         byte[] data = stdin.toByteArray();
1209         final int[] match = mControlPattern.getStartControlMatch(data);
1210         if (match != null) {
1211             stdin.reset();
1212             stdin.write(data, match[1], data.length - match[1]);
1213             return true;
1214         }
1215         return false;
1216     }
1217
1218     /**
1219      * Method that returns if the command has finished by checking the
1220      * standard input buffer.
1221      *
1222      * @param stdin The standard in buffer
1223      * @return boolean If the command has finished
1224      * @hide
1225      */
1226     boolean isCommandFinished(ByteArrayOutputStream stdin, ByteArrayOutputStream partial) {
1227         if (stdin == null) return false;
1228
1229         int[] match = mControlPattern.getEndControlMatch(stdin.toByteArray());
1230         boolean ret = match != null;
1231         // Remove partial
1232         if (ret && partial != null) {
1233
1234             byte[] bytes = partial.toByteArray();
1235             match = mControlPattern.getEndControlMatch(bytes);
1236
1237             if (match != null) {
1238                 partial.reset();
1239                 partial.write(bytes, match[0], match[1]);
1240             }
1241         }
1242         return ret;
1243     }
1244
1245     /**
1246      * New data was received
1247      * @hide
1248      */
1249     void onNewData() {
1250         synchronized (this.mSync) {
1251             this.mNewData = true;
1252         }
1253     }
1254
1255     /**
1256      * Method that returns the exit code of the last executed command.
1257      *
1258      * @param stdin The standard in buffer
1259      * @return int The exit code of the last executed command
1260      */
1261     private int getExitCode(ByteArrayOutputStream stdin, boolean async) {
1262         // If process was cancelled, don't expect a exit code.
1263         // Returns always 143 code
1264         if (this.mCancelled) {
1265             return 143;
1266         }
1267
1268         byte[] bytes = stdin.toByteArray();
1269         int[] match = mControlPattern.getEndControlMatch(bytes);
1270
1271         if (match != null) {
1272             if (!async) {
1273                 mSbIn.reset();
1274                 mSbIn.write(bytes, 0, match[0]);
1275             }
1276             // find the indices of the exit code and extract it
1277             match = mControlPattern.getEndControlMatch(bytes, true);
1278             return Integer.parseInt(new String(bytes, match[0], match[1] - match[0]));
1279         }
1280         return 255;
1281     }
1282
1283     /**
1284      * Method that trim a buffer, let in the buffer some
1285      * text to ensure that the exit code is in there.
1286      *
1287      * @param sb The buffer to trim
1288      * @hide
1289      */
1290     @SuppressWarnings("static-method") void trimBuffer(ByteArrayOutputStream sb) {
1291         final int bufferSize =  mControlPattern
1292                 .getEndControlPatternLength();
1293         if (sb.size() > bufferSize) {
1294             byte[] data = sb.toByteArray();
1295             sb.reset();
1296             sb.write(data, data.length - bufferSize, bufferSize);
1297         }
1298     }
1299
1300     /**
1301      * Method that kill the current command.
1302      *
1303      * @return boolean If the program was killed
1304      * @hide
1305      */
1306     private boolean killCurrentCommand() {
1307         synchronized (this.mSync) {
1308             // Check background console
1309             try {
1310                 FileManagerApplication.getBackgroundConsole();
1311             } catch (Exception e) {
1312                 Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$
1313                 return false;
1314             }
1315
1316             if (this.mActiveCommand != null && this.mActiveCommand.getCommand() != null) {
1317                 try {
1318                     boolean isCancellable = true;
1319                     if (this.mActiveCommand instanceof AsyncResultProgram) {
1320                         final AsyncResultProgram asyncCmd =
1321                                 (AsyncResultProgram)this.mActiveCommand;
1322                         isCancellable = asyncCmd.isCancellable();
1323                     }
1324
1325                     if (isCancellable) {
1326                         try {
1327                             //Get the PIDs in background
1328                             List<Integer> pids =
1329                                     CommandHelper.getProcessesIds(
1330                                             null,
1331                                             this.mShell.getPid(),
1332                                             FileManagerApplication.getBackgroundConsole());
1333                             for (Integer pid: pids) {
1334                                 if (pid != null) {
1335                                     CommandHelper.sendSignal(
1336                                             null,
1337                                             pid.intValue(),
1338                                             FileManagerApplication.getBackgroundConsole());
1339                                     try {
1340                                         //Wait for process to be killed
1341                                         Thread.sleep(100L);
1342                                     } catch (Throwable ex) {
1343                                         /**NON BLOCK**/
1344                                     }
1345                                 }
1346                             }
1347                             return true;
1348                         } finally {
1349                             // It's finished
1350                             this.mCancelled = true;
1351                             notifyProcessFinished();
1352                             this.mSync.notify();
1353                         }
1354                     }
1355                 } catch (Throwable ex) {
1356                     Log.w(TAG,
1357                             String.format("Unable to kill current program: %s",
1358                                     (
1359                                             (this.mActiveCommand == null) ?
1360                                                     "" :
1361                                                     this.mActiveCommand.getCommand()
1362                                     )
1363                             ), ex);
1364                 }
1365             }
1366         }
1367         return false;
1368     }
1369
1370     /**
1371      * Method that send a signal to the current command.
1372      *
1373      * @param SIGNAL The signal to send
1374      * @return boolean If the signal was sent
1375      * @hide
1376      */
1377     private boolean sendSignalToCurrentCommand(SIGNAL signal) {
1378         synchronized (this.mSync) {
1379             // Check background console
1380             try {
1381                 FileManagerApplication.getBackgroundConsole();
1382             } catch (Exception e) {
1383                 Log.w(TAG, "There is not background console. Not allowed.", e); //$NON-NLS-1$
1384                 return false;
1385             }
1386
1387             if (this.mActiveCommand.getCommand() != null) {
1388                 try {
1389                     boolean isCancellable = true;
1390                     if (this.mActiveCommand instanceof AsyncResultProgram) {
1391                         final AsyncResultProgram asyncCmd =
1392                                 (AsyncResultProgram)this.mActiveCommand;
1393                         isCancellable = asyncCmd.isCancellable();
1394                     }
1395
1396                     if (isCancellable) {
1397                         try {
1398                             //Get the PIDs in background
1399                             List<Integer> pids =
1400                                     CommandHelper.getProcessesIds(
1401                                             null,
1402                                             this.mShell.getPid(),
1403                                             FileManagerApplication.getBackgroundConsole());
1404                             for (Integer pid: pids) {
1405                                 if (pid != null) {
1406                                     CommandHelper.sendSignal(
1407                                             null,
1408                                             pid.intValue(),
1409                                             signal,
1410                                             FileManagerApplication.getBackgroundConsole());
1411                                     try {
1412                                         //Wait for process to be signaled
1413                                         Thread.sleep(100L);
1414                                     } catch (Throwable ex) {
1415                                         /**NON BLOCK**/
1416                                     }
1417                                 }
1418                             }
1419                             return true;
1420                         } finally {
1421                             // It's finished
1422                             this.mCancelled = true;
1423                             notifyProcessFinished();
1424                             this.mSync.notify();
1425                         }
1426                     }
1427                 } catch (Throwable ex) {
1428                     Log.w(TAG,
1429                         String.format("Unable to send signal to current program: %s", //$NON-NLS-1$
1430                                 this.mActiveCommand.getCommand()), ex);
1431                 }
1432             }
1433         }
1434         return false;
1435     }
1436
1437     /**
1438      * {@inheritDoc}
1439      */
1440     @Override
1441     public boolean onEnd() {
1442         //Kill the current command on end request
1443         return killCurrentCommand();
1444     }
1445
1446     /**
1447      * {@inheritDoc}
1448      */
1449     @Override
1450     public boolean onSendSignal(SIGNAL signal) {
1451         //Send a signal to the current command on end request
1452         return sendSignalToCurrentCommand(signal);
1453     }
1454
1455     /**
1456      * {@inheritDoc}
1457      */
1458     @Override
1459     public boolean onCancel() {
1460         //Kill the current command on cancel request
1461         return killCurrentCommand();
1462     }
1463
1464     /**
1465      * {@inheritDoc}
1466      */
1467     @Override
1468     public boolean onRequestWrite(
1469             byte[] data, int offset, int byteCount) throws ExecutionException {
1470         try {
1471             // Method that write to the stdin the data requested by the program
1472             if (this.mOut != null) {
1473                 this.mOut.write(data, offset, byteCount);
1474                 this.mOut.flush();
1475                 Thread.yield();
1476                 return true;
1477             }
1478         } catch (Exception ex) {
1479             String msg = String.format("Unable to write data to program: %s", //$NON-NLS-1$
1480                                                 this.mActiveCommand.getCommand());
1481             Log.e(TAG, msg, ex);
1482             throw new ExecutionException(msg, ex);
1483         }
1484         return false;
1485     }
1486
1487     /**
1488      * {@inheritDoc}
1489      */
1490     @Override
1491     public OutputStream getOutputStream() {
1492         return this.mOut;
1493     }
1494
1495 }