Add support for stdin for background TermuxTasks

This will allow passing scripts (to bash or python) or other data to an executable via stdin. Arguments are passed to the executable and not the script.
This commit is contained in:
agnostic-apollo
2021-04-12 18:25:43 +05:00
parent 824b3e657f
commit 192b208883
5 changed files with 70 additions and 18 deletions

View File

@@ -392,8 +392,8 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
/** Create a {@link TermuxTask}. */ /** Create a {@link TermuxTask}. */
@Nullable @Nullable
public TermuxTask createTermuxTask(String executablePath, String[] arguments, String workingDirectory) { public TermuxTask createTermuxTask(String executablePath, String[] arguments, String stdin, String workingDirectory) {
return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, true, false)); return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, true, false));
} }
/** Create a {@link TermuxTask}. */ /** 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}. * Currently called by {@link TermuxTerminalSessionClient#addNewSession(boolean, String)} to add a new {@link TermuxSession}.
*/ */
@Nullable @Nullable
public TermuxSession createTermuxSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe, String sessionName) { public TermuxSession createTermuxSession(String executablePath, String[] arguments, String stdin, String workingDirectory, boolean isFailSafe, String sessionName) {
return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, false, isFailSafe), sessionName); return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, false, isFailSafe), sessionName);
} }
/** Create a {@link TermuxSession}. */ /** Create a {@link TermuxSession}. */

View File

@@ -206,7 +206,7 @@ public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase
workingDirectory = currentSession.getCwd(); 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; if (newTermuxSession == null) return;
TerminalSession newTerminalSession = newTermuxSession.getTerminalSession(); TerminalSession newTerminalSession = newTermuxSession.getTerminalSession();

View File

@@ -74,6 +74,8 @@ public class ExecutionCommand {
public Uri executableUri; public Uri executableUri;
/** The executable arguments array for the {@link ExecutionCommand}. */ /** The executable arguments array for the {@link ExecutionCommand}. */
public String[] arguments; public String[] arguments;
/** The stdin string for the {@link ExecutionCommand}. */
public String stdin;
/** The current working directory for the {@link ExecutionCommand}. */ /** The current working directory for the {@link ExecutionCommand}. */
public String workingDirectory; public String workingDirectory;
@@ -138,10 +140,11 @@ public class ExecutionCommand {
this.id = id; 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.id = id;
this.executable = executable; this.executable = executable;
this.arguments = arguments; this.arguments = arguments;
this.stdin = stdin;
this.workingDirectory = workingDirectory; this.workingDirectory = workingDirectory;
this.inBackground = inBackground; this.inBackground = inBackground;
this.isFailsafe = isFailsafe; this.isFailsafe = isFailsafe;
@@ -560,4 +563,8 @@ public class ExecutionCommand {
return currentState == ExecutionState.EXECUTING; return currentState == ExecutionState.EXECUTING;
} }
public synchronized boolean isSuccessful() {
return currentState == ExecutionState.SUCCESS;
}
} }

View File

@@ -13,8 +13,10 @@ import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.logger.Logger; import com.termux.shared.logger.Logger;
import com.termux.shared.models.ExecutionCommand.ExecutionState; import com.termux.shared.models.ExecutionCommand.ExecutionState;
import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; 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)}. * 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) { if (isSynchronous) {
try { try {
termuxTask.executeInner(); termuxTask.executeInner(context);
} catch (IllegalThreadStateException | InterruptedException e) { } catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned? // TODO: Should either of these be handled or returned?
} }
@@ -101,7 +103,7 @@ public final class TermuxTask {
@Override @Override
public void run() { public void run() {
try { try {
termuxTask.executeInner(); termuxTask.executeInner(context);
} catch (IllegalThreadStateException | InterruptedException e) { } catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned? // 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} * 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 {@link ExecutionCommand#exitCode} for the {@link #mExecutionCommand} of the {@code termuxTask}
* and then calls {@link #processTermuxTaskResult(TermuxTask, ExecutionCommand) to process the result}. * 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); final int pid = ShellUtils.getPid(mProcess);
Logger.logDebug(LOG_TAG, "Running \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid); Logger.logDebug(LOG_TAG, "Running \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid);
@@ -129,8 +133,8 @@ public final class TermuxTask {
mExecutionCommand.exitCode = null; mExecutionCommand.exitCode = null;
// setup stdin, and stdout and stderr gobblers
// setup stdout and stderr gobblers DataOutputStream STDIN = new DataOutputStream(mProcess.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", mProcess.getInputStream(), mStdout); StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", mProcess.getInputStream(), mStdout);
StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", mProcess.getErrorStream(), mStderr); StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", mProcess.getErrorStream(), mStderr);
@@ -138,6 +142,33 @@ public final class TermuxTask {
STDOUT.start(); STDOUT.start();
STDERR.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 // wait for our process to finish, while we gobble away in the background
int exitCode = mProcess.waitFor(); int exitCode = mProcess.waitFor();
@@ -146,6 +177,11 @@ public final class TermuxTask {
// needed in theory, and may even produce warnings, in "normal" Java // needed in theory, and may even produce warnings, in "normal" Java
// they are required for guaranteed cleanup of resources, so lets be // they are required for guaranteed cleanup of resources, so lets be
// safe and do this on Android as well // safe and do this on Android as well
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join(); STDOUT.join();
STDERR.join(); STDERR.join();
mProcess.destroy(); mProcess.destroy();
@@ -201,13 +237,20 @@ public final class TermuxTask {
} }
if (mExecutionCommand.isExecuting()) { if (mExecutionCommand.isExecuting()) {
int pid = ShellUtils.getPid(mProcess); kill();
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 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());
} }
} }

View File

@@ -82,6 +82,8 @@
<string name="error_execution_cancelled">Execution has been cancelled since execution service is being killed</string> <string name="error_execution_cancelled">Execution has been cancelled since execution service is being killed</string>
<string name="error_failed_to_execute_termux_session_command">"Failed to execute \"%1$s\" termux session command"</string> <string name="error_failed_to_execute_termux_session_command">"Failed to execute \"%1$s\" termux session command"</string>
<string name="error_failed_to_execute_termux_task_command">"Failed to execute \"%1$s\" termux task command"</string> <string name="error_failed_to_execute_termux_task_command">"Failed to execute \"%1$s\" termux task command"</string>
<string name="error_exception_received_while_executing_termux_session_command">Exception received while to executing \"%1$s\" termux session command.\nException: %2$s</string>
<string name="error_exception_received_while_executing_termux_task_command">Exception received while to executing \"%1$s\" termux task command.\nException: %2$s"</string>