diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 26e89309..135dcc85 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -392,8 +392,8 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas /** Create a {@link TermuxTask}. */ @Nullable - public TermuxTask createTermuxTask(String executablePath, String[] arguments, String workingDirectory) { - return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, true, false)); + public TermuxTask createTermuxTask(String executablePath, String[] arguments, String stdin, String workingDirectory) { + return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, true, false)); } /** Create a {@link TermuxTask}. */ @@ -479,8 +479,8 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas * Currently called by {@link TermuxTerminalSessionClient#addNewSession(boolean, String)} to add a new {@link TermuxSession}. */ @Nullable - public TermuxSession createTermuxSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe, String sessionName) { - return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, false, isFailSafe), sessionName); + public TermuxSession createTermuxSession(String executablePath, String[] arguments, String stdin, String workingDirectory, boolean isFailSafe, String sessionName) { + return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, false, isFailSafe), sessionName); } /** Create a {@link TermuxSession}. */ diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java index 13d5934e..8beb7971 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java @@ -206,7 +206,7 @@ public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase workingDirectory = currentSession.getCwd(); } - TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, workingDirectory, isFailSafe, sessionName); + TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, null, workingDirectory, isFailSafe, sessionName); if (newTermuxSession == null) return; TerminalSession newTerminalSession = newTermuxSession.getTerminalSession(); diff --git a/termux-shared/src/main/java/com/termux/shared/models/ExecutionCommand.java b/termux-shared/src/main/java/com/termux/shared/models/ExecutionCommand.java index 5aaf585b..76e58532 100644 --- a/termux-shared/src/main/java/com/termux/shared/models/ExecutionCommand.java +++ b/termux-shared/src/main/java/com/termux/shared/models/ExecutionCommand.java @@ -74,6 +74,8 @@ public class ExecutionCommand { public Uri executableUri; /** The executable arguments array for the {@link ExecutionCommand}. */ public String[] arguments; + /** The stdin string for the {@link ExecutionCommand}. */ + public String stdin; /** The current working directory for the {@link ExecutionCommand}. */ public String workingDirectory; @@ -138,10 +140,11 @@ public class ExecutionCommand { this.id = id; } - public ExecutionCommand(Integer id, String executable, String[] arguments, String workingDirectory, boolean inBackground, boolean isFailsafe) { + public ExecutionCommand(Integer id, String executable, String[] arguments, String stdin, String workingDirectory, boolean inBackground, boolean isFailsafe) { this.id = id; this.executable = executable; this.arguments = arguments; + this.stdin = stdin; this.workingDirectory = workingDirectory; this.inBackground = inBackground; this.isFailsafe = isFailsafe; @@ -560,4 +563,8 @@ public class ExecutionCommand { return currentState == ExecutionState.EXECUTING; } + public synchronized boolean isSuccessful() { + return currentState == ExecutionState.SUCCESS; + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java b/termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java index 17d6795f..d8525430 100644 --- a/termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java +++ b/termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java @@ -13,8 +13,10 @@ import com.termux.shared.termux.TermuxConstants; import com.termux.shared.logger.Logger; import com.termux.shared.models.ExecutionCommand.ExecutionState; +import java.io.DataOutputStream; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; /** * A class that maintains info for background Termux tasks run with {@link Runtime#exec(String[], String[], File)}. @@ -92,7 +94,7 @@ public final class TermuxTask { if (isSynchronous) { try { - termuxTask.executeInner(); + termuxTask.executeInner(context); } catch (IllegalThreadStateException | InterruptedException e) { // TODO: Should either of these be handled or returned? } @@ -101,7 +103,7 @@ public final class TermuxTask { @Override public void run() { try { - termuxTask.executeInner(); + termuxTask.executeInner(context); } catch (IllegalThreadStateException | InterruptedException e) { // TODO: Should either of these be handled or returned? } @@ -118,8 +120,10 @@ public final class TermuxTask { * If the processes finishes, then sets {@link ExecutionCommand#stdout}, {@link ExecutionCommand#stderr} * and {@link ExecutionCommand#exitCode} for the {@link #mExecutionCommand} of the {@code termuxTask} * and then calls {@link #processTermuxTaskResult(TermuxTask, ExecutionCommand) to process the result}. + * + * @param context The {@link Context} for operations. */ - private void executeInner() throws IllegalThreadStateException, InterruptedException { + private void executeInner(@NonNull final Context context) throws IllegalThreadStateException, InterruptedException { final int pid = ShellUtils.getPid(mProcess); Logger.logDebug(LOG_TAG, "Running \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid); @@ -129,8 +133,8 @@ public final class TermuxTask { mExecutionCommand.exitCode = null; - - // setup stdout and stderr gobblers + // setup stdin, and stdout and stderr gobblers + DataOutputStream STDIN = new DataOutputStream(mProcess.getOutputStream()); StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", mProcess.getInputStream(), mStdout); StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", mProcess.getErrorStream(), mStderr); @@ -138,6 +142,33 @@ public final class TermuxTask { STDOUT.start(); STDERR.start(); + if (mExecutionCommand.stdin != null && !mExecutionCommand.stdin.isEmpty()) { + try { + STDIN.write((mExecutionCommand.stdin + "\n").getBytes(StandardCharsets.UTF_8)); + STDIN.flush(); + STDIN.close(); + //STDIN.write("exit\n".getBytes(StandardCharsets.UTF_8)); + //STDIN.flush(); + } catch(IOException e){ + if (e.getMessage().contains("EPIPE") || e.getMessage().contains("Stream closed")) { + // Method most horrid to catch broken pipe, in which case we + // do nothing. The command is not a shell, the shell closed + // STDIN, the script already contained the exit command, etc. + // these cases we want the output instead of returning null. + } else { + // other issues we don't know how to handle, leads to + // returning null + mExecutionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_exception_received_while_executing_termux_task_command, mExecutionCommand.getCommandIdAndLabelLogString(), e.getMessage()), e); + mExecutionCommand.stdout = mStdout.toString(); + mExecutionCommand.stderr = mStderr.toString(); + mExecutionCommand.exitCode = -1; + TermuxTask.processTermuxTaskResult(this, null); + kill(); + return; + } + } + } + // wait for our process to finish, while we gobble away in the background int exitCode = mProcess.waitFor(); @@ -146,6 +177,11 @@ public final class TermuxTask { // needed in theory, and may even produce warnings, in "normal" Java // they are required for guaranteed cleanup of resources, so lets be // safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + // might be closed already + } STDOUT.join(); STDERR.join(); mProcess.destroy(); @@ -201,13 +237,20 @@ public final class TermuxTask { } if (mExecutionCommand.isExecuting()) { - int pid = ShellUtils.getPid(mProcess); - try { - // Send SIGKILL to process - Os.kill(pid, OsConstants.SIGKILL); - } catch (ErrnoException e) { - Logger.logWarn(LOG_TAG, "Failed to send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + ": " + e.getMessage()); - } + kill(); + } + } + + /** + * Kill this {@link TermuxTask} by sending a {@link OsConstants#SIGILL} to its {@link #mProcess}. + */ + public void kill() { + int pid = ShellUtils.getPid(mProcess); + try { + // Send SIGKILL to process + Os.kill(pid, OsConstants.SIGKILL); + } catch (ErrnoException e) { + Logger.logWarn(LOG_TAG, "Failed to send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + ": " + e.getMessage()); } } diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index a4b77fc5..d1b4e494 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -82,6 +82,8 @@ Execution has been cancelled since execution service is being killed "Failed to execute \"%1$s\" termux session command" "Failed to execute \"%1$s\" termux task command" + Exception received while to executing \"%1$s\" termux session command.\nException: %2$s + Exception received while to executing \"%1$s\" termux task command.\nException: %2$s"