Skip to content

Commit

Permalink
Add support for stdin for background TermuxTasks
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
agnostic-apollo committed Apr 12, 2021
1 parent 824b3e6 commit 192b208
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 18 deletions.
8 changes: 4 additions & 4 deletions app/src/main/java/com/termux/app/TermuxService.java
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ private void executeTermuxTaskCommand(ExecutionCommand executionCommand) {

/** 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}. */
Expand Down Expand Up @@ -479,8 +479,8 @@ private void executeTermuxSessionCommand(ExecutionCommand executionCommand) {
* 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}. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void addNewSession(boolean isFailSafe, String sessionName) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public int getValue() {
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;

Expand Down Expand Up @@ -138,10 +140,11 @@ public ExecutionCommand(Integer 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.executable = executable;
this.arguments = arguments;
this.stdin = stdin;
this.workingDirectory = workingDirectory;
this.inBackground = inBackground;
this.isFailsafe = isFailsafe;
Expand Down Expand Up @@ -560,4 +563,8 @@ public synchronized boolean isExecuting() {
return currentState == ExecutionState.EXECUTING;
}

public synchronized boolean isSuccessful() {
return currentState == ExecutionState.SUCCESS;
}

}
67 changes: 55 additions & 12 deletions termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
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)}.
Expand Down Expand Up @@ -92,7 +94,7 @@ public static TermuxTask execute(@NonNull final Context context, @NonNull Execut

if (isSynchronous) {
try {
termuxTask.executeInner();
termuxTask.executeInner(context);
} catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned?
}
Expand All @@ -101,7 +103,7 @@ public static TermuxTask execute(@NonNull final Context context, @NonNull Execut
@Override
public void run() {
try {
termuxTask.executeInner();
termuxTask.executeInner(context);
} catch (IllegalThreadStateException | InterruptedException e) {
// TODO: Should either of these be handled or returned?
}
Expand All @@ -118,8 +120,10 @@ public void run() {
* 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);
Expand All @@ -129,15 +133,42 @@ private void executeInner() throws IllegalThreadStateException, InterruptedExcep
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);

// start gobbling
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();

Expand All @@ -146,6 +177,11 @@ private void executeInner() throws IllegalThreadStateException, InterruptedExcep
// 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();
Expand Down Expand Up @@ -201,13 +237,20 @@ public void killIfExecuting(@NonNull final Context context, boolean processResul
}

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());
}
}

Expand Down
2 changes: 2 additions & 0 deletions termux-shared/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
<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_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>



Expand Down

0 comments on commit 192b208

Please sign in to comment.